| /* |
| * 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 |