blob: 4bf217b58b801211376a1f69ca1fd952f152315b [file] [log] [blame]
/*
* Copyright 2015 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "cobalt/renderer/test/png_utils/png_decode.h"
#include <vector>
#include "base/debug/trace_event.h"
#include "base/file_util.h"
#include "base/logging.h"
#include "third_party/libpng/png.h"
namespace cobalt {
namespace renderer {
namespace test {
namespace png_utils {
namespace {
class PNGFileReadContext {
public:
explicit PNGFileReadContext(const FilePath& file_path,
render_tree::PixelFormat pixel_format,
render_tree::AlphaFormat alpha_format);
~PNGFileReadContext();
void DecodeImageTo(const std::vector<png_bytep>& rows);
int width() const { return width_; }
int height() const { return height_; }
private:
base::PlatformFile file_;
png_structp png_;
png_infop png_metadata_;
int width_;
int height_;
};
uint8_t PremultiplyAlpha(uint8_t alpha_value, uint8_t component_value) {
// The following code divides by 255 and rounds without explicitly using
// a division instruction.
// For more information read:
// http://answers.google.com/answers/threadview/id/502016.html
uint32_t product = component_value * alpha_value + 128;
return (product + (product >> 8)) >> 8;
}
template <bool premultiply, int r, int g, int b, int a>
void TransformPixelRow(png_structp ptr, png_row_infop row_info,
png_bytep data) {
for (unsigned int i = 0; i < row_info->width; ++i) {
uint8_t* color_bytes = data + i * 4;
uint8_t pixel[4];
for (int i = 0; i < 4; ++i) {
pixel[i] = color_bytes[i];
}
uint8_t alpha_value = pixel[3];
color_bytes[r] =
premultiply ? PremultiplyAlpha(alpha_value, pixel[0]) : pixel[0];
color_bytes[g] =
premultiply ? PremultiplyAlpha(alpha_value, pixel[1]) : pixel[1];
color_bytes[b] =
premultiply ? PremultiplyAlpha(alpha_value, pixel[2]) : pixel[2];
color_bytes[a] = pixel[3];
}
}
void PNGReadPlatformFile(png_structp png, png_bytep buffer,
png_size_t buffer_size) {
#if defined(OS_STARBOARD)
// Casting between two pointer types.
base::PlatformFile file =
reinterpret_cast<base::PlatformFile>(png_get_io_ptr(png));
#else
// Casting from a pointer to an int type.
intptr_t temp = reinterpret_cast<intptr_t>(png_get_io_ptr(png));
base::PlatformFile file = static_cast<base::PlatformFile>(temp);
#endif
int count = base::ReadPlatformFileAtCurrentPos(
file, reinterpret_cast<char*>(buffer), buffer_size);
DCHECK_EQ(count, buffer_size);
}
PNGFileReadContext::PNGFileReadContext(const FilePath& file_path,
render_tree::PixelFormat pixel_format,
render_tree::AlphaFormat alpha_format) {
TRACE_EVENT0("renderer::test::png_utils",
"PNGFileReadContext::PNGFileReadContext()");
// Much of this PNG loading code is based on a section from the libpng manual:
// http://www.libpng.org/pub/png/libpng-1.2.5-manual.html#section-3
file_ = base::CreatePlatformFile(
file_path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_READ, NULL,
NULL);
DCHECK_NE(base::kInvalidPlatformFileValue, file_) << "Unable to open: "
<< file_path.value();
uint8_t header[8];
int count = base::ReadPlatformFileAtCurrentPos(
file_, reinterpret_cast<char*>(header), sizeof(header));
DCHECK_EQ(sizeof(header), count) << "Invalid file size.";
DCHECK(!png_sig_cmp(header, 0, 8)) << "Invalid PNG header.";
// Set up a libpng context for reading images.
png_ = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
DCHECK(png_);
// Create a structure to contain metadata about the image.
png_metadata_ = png_create_info_struct(png_);
DCHECK(png_metadata_);
// libpng expects to longjump to png_->jmpbuf if it encounters an error.
// According to longjmp's documentation, this implies that stack unwinding
// will occur in that case, though C++ objects with non-trivial destructors
// will not be called. This is fine though, since we abort upon errors here.
// If alternative behavior is desired, custom error and warning handler
// functions can be passed into the png_create_read_struct() call above.
if (setjmp(png_->jmpbuf)) {
LOG(FATAL) << "libpng returned error reading " << file_path.value();
}
// Set up for file i/o.
png_set_read_fn(png_, reinterpret_cast<void*>(file_), &PNGReadPlatformFile);
// Tell png we already read 8 bytes.
png_set_sig_bytes(png_, 8);
// Read the image info.
png_read_info(png_, png_metadata_);
// Transform PNGs into a canonical RGBA form, in order to simplify the process
// of reading png files of varying formats. Of course, if we would like to
// maintain data in formats other than RGBA, this logic should be adjusted.
{
if (png_get_bit_depth(png_, png_metadata_) == 16) {
png_set_strip_16(png_);
}
png_byte color = png_get_color_type(png_, png_metadata_);
if (color == PNG_COLOR_TYPE_GRAY &&
png_get_bit_depth(png_, png_metadata_) < 8) {
png_set_expand_gray_1_2_4_to_8(png_);
}
// Convert from grayscale or palette to color.
if (!(color & PNG_COLOR_MASK_COLOR)) {
png_set_gray_to_rgb(png_);
} else if (color == PNG_COLOR_TYPE_PALETTE) {
png_set_palette_to_rgb(png_);
}
// Add an alpha channel if missing.
if (!(color & PNG_COLOR_MASK_ALPHA)) {
png_set_add_alpha(png_, 0xff /* opaque */, PNG_FILLER_AFTER);
}
}
switch (alpha_format) {
case render_tree::kAlphaFormatPremultiplied: {
switch (pixel_format) {
case render_tree::kPixelFormatRGBA8: {
png_set_read_user_transform_fn(png_,
&TransformPixelRow<true, 0, 1, 2, 3>);
} break;
case render_tree::kPixelFormatBGRA8: {
png_set_read_user_transform_fn(png_,
&TransformPixelRow<true, 2, 1, 0, 3>);
} break;
case render_tree::kPixelFormatY8:
case render_tree::kPixelFormatU8:
case render_tree::kPixelFormatV8:
case render_tree::kPixelFormatUV8:
case render_tree::kPixelFormatInvalid: {
NOTREACHED();
}
}
} break;
case render_tree::kAlphaFormatUnpremultiplied: {
switch (pixel_format) {
case render_tree::kPixelFormatRGBA8: {
// No need to set a transform in this case, this is how the data
// is coming out anyway.
} break;
case render_tree::kPixelFormatBGRA8: {
png_set_read_user_transform_fn(png_,
&TransformPixelRow<true, 2, 1, 0, 3>);
} break;
case render_tree::kPixelFormatY8:
case render_tree::kPixelFormatU8:
case render_tree::kPixelFormatV8:
case render_tree::kPixelFormatUV8:
case render_tree::kPixelFormatInvalid: {
NOTREACHED();
}
}
} break;
}
// End transformations. Get the updated info, and then verify.
png_read_update_info(png_, png_metadata_);
DCHECK_EQ(PNG_COLOR_TYPE_RGBA, png_get_color_type(png_, png_metadata_));
DCHECK_EQ(8, png_get_bit_depth(png_, png_metadata_));
width_ = png_get_image_width(png_, png_metadata_);
height_ = png_get_image_height(png_, png_metadata_);
}
PNGFileReadContext::~PNGFileReadContext() {
// Time to clean up. First create a structure to read image metadata (like
// comments) from the end of the png file, then read the remaining data in
// the png file, and then finally release our context and close the file.
png_infop end = png_create_info_struct(png_);
DCHECK(end);
// Read the end data in the png file.
png_read_end(png_, end);
// Release our png reading context and associated info structs.
png_destroy_read_struct(&png_, &png_metadata_, &end);
base::ClosePlatformFile(file_);
}
void PNGFileReadContext::DecodeImageTo(const std::vector<png_bytep>& rows) {
TRACE_EVENT0("renderer::test::png_utils",
"PNGFileReadContext::DecodeImageTo()");
// Execute the read of png image data.
png_read_image(png_, const_cast<png_bytep*>(&rows[0]));
}
scoped_array<uint8_t> DecodePNGToRGBAInternal(
const FilePath& png_file_path, int* width, int* height,
render_tree::AlphaFormat alpha_format) {
PNGFileReadContext png_read_context(
png_file_path, render_tree::kPixelFormatRGBA8, alpha_format);
// Setup pointers to the rows in which libpng should read out the decoded png
// image data to.
// We set bpp to 4 because we know that we're dealing with RGBA.
const int bytes_per_pixel = 4;
int pitch = png_read_context.width() * 4;
scoped_array<uint8_t> data(new uint8_t[pitch * png_read_context.height()]);
std::vector<png_bytep> rows(png_read_context.height());
uint8_t* row = data.get();
for (int i = 0; i < png_read_context.height(); ++i) {
rows[i] = row;
row += pitch;
}
png_read_context.DecodeImageTo(rows);
// And finally return the output decoded PNG data.
DCHECK(width);
DCHECK(height);
*width = png_read_context.width();
*height = png_read_context.height();
return data.Pass();
}
} // namespace
scoped_array<uint8_t> DecodePNGToRGBA(const FilePath& png_file_path, int* width,
int* height) {
TRACE_EVENT0("renderer::test::png_utils", "DecodePNGToRGBA()");
return DecodePNGToRGBAInternal(png_file_path, width, height,
render_tree::kAlphaFormatUnpremultiplied);
}
scoped_array<uint8_t> DecodePNGToPremultipliedAlphaRGBA(
const FilePath& png_file_path, int* width, int* height) {
TRACE_EVENT0("renderer::test::png_utils",
"DecodePNGToPremultipliedAlphaRGBA()");
return DecodePNGToRGBAInternal(png_file_path, width, height,
render_tree::kAlphaFormatPremultiplied);
}
namespace {
render_tree::PixelFormat ChoosePixelFormat(
render_tree::ResourceProvider* resource_provider) {
if (resource_provider->PixelFormatSupported(render_tree::kPixelFormatRGBA8)) {
return render_tree::kPixelFormatRGBA8;
} else if (resource_provider->PixelFormatSupported(
render_tree::kPixelFormatBGRA8)) {
return render_tree::kPixelFormatBGRA8;
} else {
NOTREACHED() << "Invalid pixel format.";
return render_tree::kPixelFormatInvalid;
}
}
} // namespace
scoped_refptr<cobalt::render_tree::Image> DecodePNGToRenderTreeImage(
const FilePath& png_file_path,
render_tree::ResourceProvider* resource_provider) {
TRACE_EVENT0("renderer::test::png_utils", "DecodePNGToRenderTreeImage()");
render_tree::PixelFormat pixel_format = ChoosePixelFormat(resource_provider);
PNGFileReadContext png_read_context(png_file_path, pixel_format,
render_tree::kAlphaFormatPremultiplied);
// Setup pointers to the rows in which libpng should read out the decoded png
// image data to.
// Currently, we decode all images to RGBA and load those.
scoped_ptr<render_tree::ImageData> data =
resource_provider->AllocateImageData(
math::Size(png_read_context.width(), png_read_context.height()),
pixel_format, render_tree::kAlphaFormatPremultiplied);
std::vector<png_bytep> rows(png_read_context.height());
uint8_t* row = data->GetMemory();
for (int i = 0; i < png_read_context.height(); ++i) {
rows[i] = row;
row += data->GetDescriptor().pitch_in_bytes;
}
png_read_context.DecodeImageTo(rows);
// And now create a texture out of the image data.
return resource_provider->CreateImage(data.Pass());
}
} // namespace png_utils
} // namespace test
} // namespace renderer
} // namespace cobalt