| /* |
| * 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: Victor Chudnovsky |
| |
| #include "pagespeed/kernel/image/webp_optimizer.h" |
| |
| #include "base/logging.h" |
| #include "pagespeed/kernel/base/message_handler.h" |
| #include "pagespeed/kernel/image/scanline_utils.h" |
| |
| extern "C" { |
| #ifdef USE_SYSTEM_LIBWEBP |
| #include "webp/decode.h" |
| #else |
| #include "third_party/libwebp/src/webp/decode.h" |
| #endif |
| } |
| |
| namespace pagespeed { |
| |
| namespace image_compression { |
| |
| using image_compression::GetNumChannelsFromPixelFormat; |
| using net_instaweb::MessageHandler; |
| |
| // Copied from libwebp/v0_2/examples/cwebp.c |
| static const char* const kWebPErrorMessages[] = { |
| "OK", |
| "OUT_OF_MEMORY: Out of memory allocating objects", |
| "BITSTREAM_OUT_OF_MEMORY: Out of memory re-allocating byte buffer", |
| "NULL_PARAMETER: NULL parameter passed to function", |
| "INVALID_CONFIGURATION: configuration is invalid", |
| "BAD_DIMENSION: Bad picture dimension. Maximum width and height " |
| "allowed is 16383 pixels.", |
| "PARTITION0_OVERFLOW: Partition #0 is too big to fit 512k.\n" |
| "To reduce the size of this partition, try using less segments " |
| "with the -segments option, and eventually reduce the number of " |
| "header bits using -partition_limit. More details are available " |
| "in the manual (`man cwebp`)", |
| "PARTITION_OVERFLOW: Partition is too big to fit 16M", |
| "BAD_WRITE: Picture writer returned an I/O error", |
| "FILE_TOO_BIG: File would be too big to fit in 4G", |
| "USER_ABORT: encoding abort requested by user" |
| }; |
| |
| // The libwebp error code returned in case of timeouts. |
| static const int kWebPErrorTimeout = VP8_ENC_ERROR_USER_ABORT; |
| |
| void WebpConfiguration::CopyTo(WebPConfig* webp_config) const { |
| webp_config->lossless = lossless; |
| webp_config->quality = quality; |
| webp_config->method = method; |
| webp_config->target_size = target_size; |
| webp_config->alpha_compression = alpha_compression; |
| webp_config->alpha_filtering = alpha_filtering; |
| webp_config->alpha_quality = alpha_quality; |
| } |
| |
| WebpFrameWriter::WebpFrameWriter(MessageHandler* handler) : |
| MultipleFrameWriter(handler), image_spec_(NULL), next_frame_(0), |
| next_scanline_(0), empty_frame_(false), frame_stride_px_(0), |
| frame_position_px_(NULL), frame_bytes_per_pixel_(0), webp_image_(NULL), |
| webp_frame_cache_(NULL), webp_mux_(NULL), output_image_(NULL), |
| has_alpha_(false), image_prepared_(false), progress_hook_(NULL), |
| progress_hook_data_(NULL) { |
| } |
| |
| WebpFrameWriter::~WebpFrameWriter() { |
| FreeWebpStructs(); |
| } |
| |
| void WebpFrameWriter::FreeWebpStructs() { |
| // Shortcut the initial case, which will happen every time this |
| // class is used. |
| if ((webp_frame_cache_ == NULL) && |
| (webp_image_ == NULL) && |
| (webp_mux_ == NULL)) { |
| return; |
| } |
| |
| WebPFrameCacheDelete(webp_frame_cache_); |
| webp_frame_cache_ = NULL; |
| |
| WebPPictureFree(webp_image_); |
| delete webp_image_; |
| webp_image_ = NULL; |
| |
| WebPMuxDelete(webp_mux_); |
| webp_mux_ = NULL; |
| } |
| |
| |
| ScanlineStatus WebpFrameWriter::Initialize(const void* config, |
| GoogleString* out) { |
| FreeWebpStructs(); |
| webp_mux_ = WebPMuxNew(); |
| if (webp_mux_ == NULL) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, |
| "WebPMuxNew() failure"); |
| } |
| |
| if (config == NULL) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_WEBPWRITER, |
| "missing WebpConfiguration*"); |
| } |
| |
| const WebpConfiguration* webp_config = |
| static_cast<const WebpConfiguration*>(config); |
| |
| if (!WebPConfigInit(&libwebp_config_)) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, "WebPConfigInit()"); |
| } |
| |
| webp_config->CopyTo(&libwebp_config_); |
| |
| if (!WebPValidateConfig(&libwebp_config_)) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, "WebPValidateConfig()"); |
| } |
| |
| if (webp_config->progress_hook) { |
| progress_hook_ = webp_config->progress_hook; |
| progress_hook_data_ = webp_config->user_data; |
| } |
| |
| kmin_ = webp_config->kmin; |
| kmax_ = webp_config->kmax; |
| |
| output_image_ = out; |
| |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| int WebpFrameWriter::ProgressHook(int percent, const WebPPicture* picture) { |
| const WebpFrameWriter* webp_writer = |
| static_cast<WebpFrameWriter*>(picture->user_data); |
| return webp_writer->progress_hook_(percent, webp_writer->progress_hook_data_); |
| } |
| |
| ScanlineStatus WebpFrameWriter::PrepareImage(const ImageSpec* image_spec) { |
| DVLOG(1) << image_spec->ToString(); |
| if (image_prepared_) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_WEBPWRITER, "image already prepared"); |
| } |
| |
| DVLOG(1) << "PrepareImage: num_frames: " << image_spec->num_frames; |
| |
| if ((image_spec->height > WEBP_MAX_DIMENSION) || |
| (image_spec->width > WEBP_MAX_DIMENSION)) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_UNSUPPORTED_FEATURE, |
| FRAME_WEBPWRITER, |
| "each image dimension must be at most %d", |
| WEBP_MAX_DIMENSION); |
| } |
| |
| if ((image_spec->height < 1) || (image_spec->width < 1)) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_UNSUPPORTED_FEATURE, |
| FRAME_WEBPWRITER, |
| "each image dimension must be at least 1"); |
| } |
| |
| webp_image_ = new WebPPicture(); |
| |
| if (!WebPPictureInit(webp_image_)) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, "WebPPictureInit()"); |
| } |
| |
| webp_image_->width = image_spec->width; |
| webp_image_->height = image_spec->height; |
| webp_image_->use_argb = true; |
| #ifndef NDEBUG |
| webp_image_->stats = &stats_; |
| #endif |
| |
| if (!WebPPictureAlloc(webp_image_)) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, "WebPPictureAlloc()"); |
| } |
| WebPUtilClearPic(webp_image_, NULL); |
| |
| webp_image_->user_data = this; |
| if (progress_hook_) { |
| webp_image_->progress_hook = ProgressHook; |
| } |
| |
| image_spec_ = image_spec; |
| next_frame_ = 0; |
| image_prepared_ = true; |
| |
| // Key frame parameters. |
| static size_t kMax; |
| static size_t kMin; |
| if (kmin_ > 0) { |
| if (kmin_ >= kmax_) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_WEBPWRITER, |
| "Keyframe parameters error: kmin >= kmax"); |
| } else if (kmin_ < (kmax_ / 2 + 1)) { |
| return PS_LOGGED_STATUS( |
| PS_LOG_DFATAL, |
| message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_WEBPWRITER, |
| "Keyframe parameters error: kmin < (kmax / 2 + 1)"); |
| } else { |
| kMax = kmax_; |
| kMin = kmin_; |
| } |
| } else { |
| kMax = ~0; |
| kMin = kMax - 1; |
| } |
| |
| webp_frame_cache_ = WebPFrameCacheNew( |
| image_spec->width, image_spec->height, kMin, kMax, |
| false /* don't allow mixing lossy and lossless frames */); |
| |
| if (webp_frame_cache_ == NULL) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_MEMORY_ERROR, |
| FRAME_WEBPWRITER, "WebPFrameCacheNew()"); |
| } |
| |
| frame_position_px_ = NULL; |
| frame_stride_px_ = 0; |
| next_scanline_ = 0; |
| |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| WebPMuxAnimDispose FrameDisposalToWebPDisposal( |
| FrameSpec::DisposalMethod frame_disposal) { |
| switch (frame_disposal) { |
| case FrameSpec::DISPOSAL_UNKNOWN: |
| case FrameSpec::DISPOSAL_NONE: |
| return WEBP_MUX_DISPOSE_NONE; |
| case FrameSpec::DISPOSAL_BACKGROUND: |
| case FrameSpec::DISPOSAL_RESTORE: |
| return WEBP_MUX_DISPOSE_BACKGROUND; |
| } |
| return WEBP_MUX_DISPOSE_NONE; |
| } |
| |
| ScanlineStatus WebpFrameWriter::CacheCurrentFrame() { |
| // If we're not even on the first frame, no-op. |
| if (next_frame_ < 1) { |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| // Don't add empty frames. |
| if (empty_frame_) { |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| // All scanlines must be written before caching a frame. |
| if (next_scanline_ < frame_spec_.height) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_WEBPWRITER, |
| "CacheCurrentFrame: not all scanlines written"); |
| } |
| |
| // We need to pass image to add frame. |
| WebPFrameRect frame_rect = { |
| static_cast<int>(frame_spec_.left), |
| static_cast<int>(frame_spec_.top), |
| static_cast<int>(frame_spec_.width), |
| static_cast<int>(frame_spec_.height) |
| }; |
| |
| if (progress_hook_) { |
| CHECK(webp_image_->progress_hook == ProgressHook); |
| CHECK(webp_image_->user_data == this); |
| } |
| |
| struct WebPMuxFrameInfo webp_frame_info; |
| memset(&webp_frame_info, 0, sizeof(webp_frame_info)); |
| webp_frame_info.id = WEBP_CHUNK_ANMF; |
| webp_frame_info.dispose_method = |
| FrameDisposalToWebPDisposal(frame_spec_.disposal); |
| webp_frame_info.blend_method = WEBP_MUX_BLEND; |
| webp_frame_info.duration = frame_spec_.duration_ms; |
| if (!WebPFrameCacheAddFrame(webp_frame_cache_, &libwebp_config_, &frame_rect, |
| webp_image_, &webp_frame_info)) { |
| if (webp_image_->error_code == kWebPErrorTimeout) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_TIMEOUT_ERROR, |
| FRAME_WEBPWRITER, |
| "WebPFrameCacheAddFrame(): %s", |
| kWebPErrorMessages[webp_image_->error_code]); |
| } else { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, |
| "WebPFrameCacheAddFrame(): %s\n%s\n%s", |
| kWebPErrorMessages[webp_image_->error_code], |
| image_spec_->ToString().c_str(), |
| frame_spec_.ToString().c_str()); |
| } |
| } |
| |
| if (WebPFrameCacheFlush(webp_frame_cache_, false /*verbose*/, webp_mux_) != |
| WEBP_MUX_OK) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, |
| "WebPFrameCacheFlush() error"); |
| } |
| |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| ScanlineStatus WebpFrameWriter::PrepareNextFrame(const FrameSpec* frame_spec) { |
| if (!image_prepared_) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_WEBPWRITER, |
| "PrepareNextFrame: image not prepared"); |
| } |
| |
| if (next_frame_ >= image_spec_->num_frames) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_WEBPWRITER, |
| "PrepareNextFrame: no next frame"); |
| } |
| |
| ScanlineStatus status = CacheCurrentFrame(); |
| if (!status.Success()) { |
| return status; |
| } |
| |
| // Bounds-check the frame. |
| if (!image_spec_->CanContainFrame(*frame_spec)) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_WEBPWRITER, |
| "PrepareNextFrame: frame does not fit in image:\n" |
| "%s\n%s", |
| image_spec_->ToString().c_str(), |
| frame_spec->ToString().c_str()); |
| } |
| |
| ++next_frame_; |
| frame_spec_ = *frame_spec; |
| |
| should_expand_gray_to_rgb_ = false; |
| PixelFormat new_pixel_format = frame_spec_.pixel_format; |
| switch (new_pixel_format) { |
| case RGB_888: |
| has_alpha_ = false; |
| break; |
| case RGBA_8888: |
| has_alpha_ = true; |
| break; |
| case GRAY_8: |
| // GRAY_8 will be expanded to RGB_888. |
| has_alpha_ = false; |
| should_expand_gray_to_rgb_ = true; |
| new_pixel_format = RGB_888; |
| break; |
| default: |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, |
| "unknown pixel format: %d", |
| new_pixel_format); |
| } |
| DVLOG(1) << "Pixel format:" << GetPixelFormatString(frame_spec_.pixel_format); |
| |
| empty_frame_ = (frame_spec_.width < 1) || (frame_spec_.height < 1); |
| if (empty_frame_) { |
| frame_stride_px_ = frame_spec_.width; |
| frame_position_px_ = NULL; |
| } else { |
| if (!WebPPictureView(webp_image_, |
| frame_spec_.left, frame_spec_.top, |
| frame_spec_.width, frame_spec_.height, |
| &webp_frame_)) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, |
| "WebPPictureView() failure: %s", |
| frame_spec_.ToString().c_str()); |
| } |
| frame_stride_px_ = webp_frame_.argb_stride; |
| frame_position_px_ = webp_frame_.argb; |
| } |
| |
| frame_bytes_per_pixel_ = GetBytesPerPixel(frame_spec_.pixel_format); |
| next_scanline_ = 0; |
| |
| return status; |
| } |
| |
| ScanlineStatus WebpFrameWriter::WriteNextScanline(const void *scanline_bytes) { |
| if (next_scanline_ >= frame_spec_.height) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler(), |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| FRAME_WEBPWRITER, |
| "WriteNextScanline: too many scanlines"); |
| } |
| if (!empty_frame_) { |
| if (should_expand_gray_to_rgb_) { |
| // Replicate the luminance to RGB. |
| const uint8_t* const in_bytes = |
| reinterpret_cast<const uint8_t*>(scanline_bytes); |
| for (int idx = 0; idx < webp_frame_.width; ++idx) { |
| frame_position_px_[idx] = GrayscaleToPackedArgb(in_bytes[idx]); |
| } |
| } else if (has_alpha_) { |
| // Note: this branch and the next only differ in the packing |
| // function used. It is tempting to assign a function pointer |
| // based on has_alpha_ and then implement the loop only |
| // once. However, since this is an "inner loop" iterating over a |
| // series of pixels, we want to take advantage of the inline |
| // forms of the packing functions for speed. |
| for (size_t px_col = 0, byte_col = 0; |
| px_col < frame_spec_.width; |
| ++px_col, byte_col += frame_bytes_per_pixel_) { |
| frame_position_px_[px_col] = |
| RgbaToPackedArgb(static_cast<const uint8_t*>(scanline_bytes) + |
| byte_col); |
| } |
| } else { |
| for (size_t px_col = 0, byte_col = 0; |
| px_col < frame_spec_.width; |
| ++px_col, byte_col += frame_bytes_per_pixel_) { |
| frame_position_px_[px_col] = |
| RgbToPackedArgb(static_cast<const uint8_t*>(scanline_bytes) + |
| byte_col); |
| } |
| } |
| frame_position_px_ += frame_stride_px_; |
| } |
| |
| ++next_scanline_; |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| ScanlineStatus WebpFrameWriter::FinalizeWrite() { |
| ScanlineStatus status = CacheCurrentFrame(); |
| if (!status.Success()) { |
| return status; |
| } |
| |
| if (WebPFrameCacheFlushAll(webp_frame_cache_, false /*verbose*/, webp_mux_) != |
| WEBP_MUX_OK) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, |
| "WebPFrameCacheFlushAll error"); |
| } |
| |
| if (next_frame_ > 1) { |
| // This was an animated image. |
| WebPMuxAnimParams anim = { |
| RgbaToPackedArgb(image_spec_->bg_color), |
| static_cast<int>(image_spec_->loop_count - 1) |
| }; |
| if (WebPMuxSetAnimationParams(webp_mux_, &anim) != WEBP_MUX_OK) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, |
| "WebPMuxSetAnimationParams error"); |
| } |
| } |
| |
| WebPData webp_data = { NULL, 0 }; |
| WebPMuxError muxerr = WebPMuxAssemble(webp_mux_, &webp_data); |
| if (muxerr != WEBP_MUX_OK) { |
| if (webp_image_->error_code == kWebPErrorTimeout) { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_TIMEOUT_ERROR, |
| FRAME_WEBPWRITER, |
| "WebPMuxAssemble: (%d) %d", |
| webp_image_->error_code, |
| muxerr); |
| } else { |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler(), |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| FRAME_WEBPWRITER, |
| "WebPMuxAssemble: (%d) %d", |
| webp_image_->error_code, |
| muxerr); |
| } |
| } |
| |
| output_image_->append(reinterpret_cast<const char *>(webp_data.bytes), |
| webp_data.size); |
| WebPDataClear(&webp_data); |
| |
| PS_DLOG_INFO(message_handler(), \ |
| "Stats: coded_size: %d; lossless_size: %d; alpha size: %d;", |
| stats_.coded_size, stats_.lossless_size, stats_.alpha_data_size); |
| |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| WebpScanlineReader::WebpScanlineReader(MessageHandler* handler) |
| : image_buffer_(NULL), |
| buffer_length_(0), |
| pixel_format_(UNSUPPORTED), |
| height_(0), |
| width_(0), |
| bytes_per_row_(0), |
| row_(0), |
| was_initialized_(false), |
| message_handler_(handler) { |
| } |
| |
| WebpScanlineReader::~WebpScanlineReader() { |
| Reset(); |
| } |
| |
| bool WebpScanlineReader::Reset() { |
| image_buffer_ = NULL; |
| buffer_length_ = 0; |
| pixel_format_ = UNSUPPORTED; |
| height_ = 0; |
| width_ = 0; |
| bytes_per_row_ = 0; |
| row_ = 0; |
| pixels_.reset(); |
| was_initialized_ = false; |
| return true; |
| } |
| |
| // Initialize the reader with the given image stream. Note that image_buffer |
| // must remain unchanged until the *first* call to ReadNextScanline(). |
| ScanlineStatus WebpScanlineReader::InitializeWithStatus( |
| const void* image_buffer, |
| size_t buffer_length) { |
| if (was_initialized_) { |
| Reset(); |
| } |
| |
| WebPBitstreamFeatures features; |
| if (WebPGetFeatures(reinterpret_cast<const uint8_t*>(image_buffer), |
| buffer_length, &features) |
| != VP8_STATUS_OK) { |
| return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_, |
| SCANLINE_STATUS_PARSE_ERROR, |
| SCANLINE_WEBPREADER, "WebPGetFeatures()"); |
| } |
| |
| |
| // Determine the pixel format and the number of channels. |
| if (features.has_alpha) { |
| pixel_format_ = RGBA_8888; |
| } else { |
| pixel_format_ = RGB_888; |
| } |
| |
| // Copy the information to the object properties. |
| image_buffer_ = reinterpret_cast<const uint8_t*>(image_buffer); |
| buffer_length_ = buffer_length; |
| width_ = features.width; |
| height_ = features.height; |
| bytes_per_row_ = width_ * GetNumChannelsFromPixelFormat(pixel_format_, |
| message_handler_); |
| row_ = 0; |
| was_initialized_ = true; |
| |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| ScanlineStatus WebpScanlineReader::ReadNextScanlineWithStatus( |
| void** out_scanline_bytes) { |
| if (!was_initialized_ || !HasMoreScanLines()) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| SCANLINE_WEBPREADER, |
| "The reader was not initialized or the image does " |
| "not have any more scanlines."); |
| } |
| |
| // The first time ReadNextScanline() is called, we decode the entire image. |
| if (row_ == 0) { |
| pixels_.reset(new uint8_t[bytes_per_row_ * height_]); |
| if (pixels_ == NULL) { |
| Reset(); |
| return PS_LOGGED_STATUS(PS_LOG_ERROR, message_handler_, |
| SCANLINE_STATUS_MEMORY_ERROR, |
| SCANLINE_WEBPREADER, |
| "Failed to allocate memory."); |
| } |
| |
| WebPDecoderConfig config; |
| CHECK(WebPInitDecoderConfig(&config)); |
| |
| // Specify the desired output colorspace: |
| if (pixel_format_ == RGB_888) { |
| config.output.colorspace = MODE_RGB; |
| } else { |
| config.output.colorspace = MODE_RGBA; |
| } |
| |
| // Have config.output point to an external buffer: |
| config.output.u.RGBA.rgba = pixels_.get(); |
| config.output.u.RGBA.stride = bytes_per_row_; |
| config.output.u.RGBA.size = bytes_per_row_ * height_; |
| config.output.is_external_memory = true; |
| |
| bool decode_ok = (WebPDecode(image_buffer_, buffer_length_, &config) |
| == VP8_STATUS_OK); |
| |
| // Clean up WebP decoder because it is not needed any more, |
| // regardless of whether whether decoding was successful or not. |
| WebPFreeDecBuffer(&config.output); |
| |
| if (!decode_ok) { |
| Reset(); |
| return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_WEBPREADER, |
| "Failed to decode the WebP image."); |
| } |
| } |
| |
| // Point output to the corresponding row of the already decoded image. |
| *out_scanline_bytes = |
| static_cast<void*>(pixels_.get() + row_ * bytes_per_row_); |
| |
| ++row_; |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| } // namespace image_compression |
| |
| } // namespace pagespeed |