| /* |
| * Copyright 2009 Google Inc. |
| * |
| * 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. |
| */ |
| |
| // Author: Bryan McQuade |
| |
| #ifndef PAGESPEED_KERNEL_IMAGE_GIF_READER_H_ |
| #define PAGESPEED_KERNEL_IMAGE_GIF_READER_H_ |
| |
| #include <cstddef> |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/message_handler.h" |
| #include "pagespeed/kernel/base/scoped_ptr.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/image/image_frame_interface.h" |
| #include "pagespeed/kernel/image/image_util.h" |
| #include "pagespeed/kernel/image/png_optimizer.h" |
| #include "pagespeed/kernel/image/scanline_status.h" |
| #include "pagespeed/kernel/image/scanline_utils.h" |
| |
| extern "C" { |
| #include "third_party/giflib/lib/gif_lib.h" |
| |
| #ifdef USE_SYSTEM_LIBPNG |
| #include "png.h" // NOLINT |
| #else |
| #include "third_party/libpng/src/png.h" |
| #endif |
| } |
| |
| namespace pagespeed { |
| |
| namespace image_compression { |
| |
| using net_instaweb::MessageHandler; |
| |
| class ScopedGifStruct; |
| |
| // Utility to translate the frame (GIF "image") disposal method from |
| // the value encoded in the GIF file to the DisposalMethod enum. |
| FrameSpec::DisposalMethod GifDisposalToFrameSpecDisposal(int gif_disposal); |
| |
| // Reader for GIF-encoded data. |
| class GifReader : public PngReaderInterface { |
| public: |
| explicit GifReader(MessageHandler* handler); |
| virtual ~GifReader(); |
| |
| virtual bool ReadPng(const GoogleString& body, |
| png_structp png_ptr, |
| png_infop info_ptr, |
| int transforms, |
| bool require_opaque) const; |
| |
| virtual bool GetAttributes(const GoogleString& body, |
| int* out_width, |
| int* out_height, |
| int* out_bit_depth, |
| int* out_color_type) const; |
| |
| private: |
| MessageHandler* message_handler_; |
| DISALLOW_COPY_AND_ASSIGN(GifReader); |
| }; |
| |
| // GifFrameReader decodes GIF images and outputs the raw pixel data, |
| // image size, pixel type, etc. The class accepts both single frame |
| // and animated GIFs. The output is RGB_888 if transparent color is |
| // not specified, or RGBA_8888 otherwise. |
| // |
| // Note: The input image stream must be valid throughout the life of |
| // the object. In other words, the image_buffer input you set to |
| // Initialize() cannot be changed until your last call to the |
| // ReadNextScanline(). That said, if you are sure that your image is |
| // a single-frame progressive GIF, you can modify image_buffer after |
| // the first call to ReadNextScanline(). |
| // |
| // Note: In the wild, there are many static GIFs that don't conform to |
| // the GIF standard by having, for example, image dimensions larger |
| // than frame dimensions. The workaround for such images is to |
| // properly pad those frames. Because this is such a common |
| // occurrence, we disallow instantiating GifFrameReader directly and |
| // instead require that clients call CreateImageFrameReader() to |
| // instantiate this class. |
| class GifFrameReader : public MultipleFrameReader { |
| public: |
| virtual ~GifFrameReader(); |
| |
| virtual ScanlineStatus Reset(); |
| |
| // Initialize the reader with the given image stream. Note that |
| // image_buffer must remain unchanged until the last call to |
| // ReadNextScanlineWithStatus(). |
| virtual ScanlineStatus Initialize(); |
| |
| virtual bool HasMoreFrames() const { |
| return (image_initialized_ && (next_frame_ < image_spec_.num_frames)); |
| } |
| |
| virtual bool HasMoreScanlines() const { |
| return (frame_initialized_ && (next_row_ < frame_spec_.height)); |
| } |
| |
| virtual ScanlineStatus PrepareNextFrame(); |
| |
| // Return the next row of pixels. For non-progressive GIF, |
| // ReadNextScanline will decode one row of pixels each time when it |
| // is called, but for progressive GIF, ReadNextScanline will decode |
| // the entire image at the first time when it is called. |
| virtual ScanlineStatus ReadNextScanline(const void** out_scanline_bytes); |
| |
| virtual ScanlineStatus GetFrameSpec(FrameSpec* frame_spec) const { |
| if (frame_spec == NULL) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_GIFREADER, |
| "Unexpected NULL pointer."); |
| } |
| *frame_spec = frame_spec_; |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| virtual ScanlineStatus GetImageSpec(ImageSpec* image_spec) const { |
| if (image_spec == NULL) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_GIFREADER, |
| "Unexpected NULL pointer."); |
| } |
| *image_spec = image_spec_; |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| virtual ScanlineStatus set_quirks_mode(QuirksMode quirks_mode); |
| |
| // Apply the specified browser-specific tweaking of image_spec based |
| // on the first frame's frame_spec and whether an explicit |
| // loop_count was read from the GIF file. |
| static void ApplyQuirksModeToImage(QuirksMode quirks_mode, |
| bool has_loop_count, |
| const FrameSpec& frame_spec, |
| ImageSpec* image_spec); |
| |
| // Apply the specified browser-specific tweaking of the first |
| // frame's frame_spec based on image_spec. |
| static void ApplyQuirksModeToFirstFrame(QuirksMode quirks_mode, |
| const ImageSpec& image_spec, |
| FrameSpec* frame_spec); |
| |
| private: |
| // Clients should call this function to instantiate |
| // GifFrameReader. This function is defined in read_image.cc. |
| friend MultipleFrameReader* InstantiateImageFrameReader( |
| ImageFormat image_type, |
| MessageHandler* handler, |
| ScanlineStatus* status); |
| |
| // Used in gif_reader_test.cc and frame_interface_integration_test.cc. |
| friend class TestGifFrameReader; |
| |
| // To instantiate this class, use InstantiateImageFrameReader, which |
| // is declared a friend function above. |
| explicit GifFrameReader(MessageHandler* handler); |
| |
| // The GIF format can specify one palette index which is treated as |
| // transparent; we store this index in frame_transparent_index_. In |
| // case the GIF file does not employ transparency, we store the |
| // special "index" value kNoTransparentIndex instead. |
| static const int kNoTransparentIndex; |
| |
| |
| // Decodes a progressive image. |
| ScanlineStatus DecodeProgressiveGif(); |
| |
| // Decodes a non-progressive image. |
| ScanlineStatus DecodeNonProgressiveGif(); |
| |
| ScanlineStatus CreateColorMap(); |
| |
| // Gets the image-scope meta-data (GIF screen size, global palette, |
| // number of frames, etc.), resetting the GIF file offset before |
| // returning. |
| ScanlineStatus GetImageData(); |
| |
| // Helper function for GetImageData() that reads the GIF application |
| // extension. |
| ScanlineStatus ProcessExtensionAffectingImage(bool past_first_frame); |
| |
| // Helper function for PrepareNextFrame that gets the frame-scope |
| // metadata (duration, disposal, etc.) from the GIF Graphics |
| // Extension Block. |
| ScanlineStatus ProcessExtensionAffectingFrame(); |
| |
| // Whether the image_has been initialized. |
| bool image_initialized_; |
| |
| // Whether the current frame has been initialized. |
| bool frame_initialized_; |
| |
| // Image metadata. |
| ImageSpec image_spec_; |
| |
| // Frame metadata. |
| FrameSpec frame_spec_; |
| |
| // Whether we've already encountered the animation loop count. |
| bool has_loop_count_; |
| |
| // The next frame to be read AFTER the current frame. |
| size_px next_frame_; |
| |
| // The following are for the current frame. |
| |
| // The next row to output via ReadScanline. |
| size_px next_row_; |
| |
| // The palette index of the transparent entry in the current frame, |
| // or kNoTransparentIndex otherwise. Set by PrepareNextFrame(). |
| int frame_transparent_index_; |
| |
| // Palette of the image, with 256 entries. |
| net_instaweb::scoped_array<PaletteRGBA> gif_palette_; |
| |
| // Buffer for holding the color (RGB or RGBA) for a row of pixels. |
| net_instaweb::scoped_array<GifByteType> frame_buffer_; |
| |
| // Buffer for holding the palette index for a row of pixels (for |
| // non-progressive GIF) or for the entire image (for progressive GIF). |
| net_instaweb::scoped_array<GifByteType> frame_index_; |
| |
| // gif_struct_ stores a pointer to the input image stream. It also |
| // keeps track of the length of data that giflib has read. It is |
| // initialized in Initialize() and is updated in |
| // ReadNextScanlineWithStatus(). |
| net_instaweb::scoped_ptr<ScopedGifStruct> gif_struct_; |
| |
| // We need to know the palette size as we read each frame because, |
| // in rare cases, images contain pixels referring to out-of-range |
| // palette entries. In that case, in PrepareNextFrame() we make the |
| // frame's pixel format be RGBA_8888 and in ReadNextScanline() we |
| // replace the invalid pixels with transparent pixels. Note that a |
| // uint8_t would be sufficient for this field, but giflib returns an |
| // int for the palette size. |
| int frame_palette_size_; |
| |
| // For frames that are either progressive or NOT in RGBA_8888 |
| // format, we set this flag in PrepareNextFrame() and then read the |
| // entire frame to see whether there are any pixels with |
| // out-of-range palette entries. If so, in PrepareNextFrame() we |
| // make the frame's pixel format be RGBA_8888 and in |
| // ReadNextScanline() we replace the invalid pixels with transparent |
| // pixels. |
| bool frame_eagerly_read_; |
| |
| DISALLOW_COPY_AND_ASSIGN(GifFrameReader); |
| }; |
| |
| } // namespace image_compression |
| |
| } // namespace pagespeed |
| |
| #endif // PAGESPEED_KERNEL_IMAGE_GIF_READER_H_ |