blob: 9aaea1d62921d5f8bf2028d14e48cd344b079872 [file] [log] [blame]
// Copyright 2016 The Cobalt Authors. 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/loader/image/webp_image_decoder.h"
#include "base/logging.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/loader/image/animated_webp_image.h"
#include "nb/memory_scope.h"
#include "starboard/configuration.h"
#include "starboard/memory.h"
namespace cobalt {
namespace loader {
namespace image {
WEBPImageDecoder::WEBPImageDecoder(
render_tree::ResourceProvider* resource_provider)
: ImageDataDecoder(resource_provider), internal_decoder_(NULL) {
TRACK_MEMORY_SCOPE("Rendering");
TRACE_EVENT0("cobalt::loader::image", "WEBPImageDecoder::WEBPImageDecoder()");
// Initialize the configuration as empty.
WebPInitDecoderConfig(&config_);
// Skip the in-loop filtering.
config_.options.bypass_filtering = 1;
// Use faster pointwise upsampler.
config_.options.no_fancy_upsampling = 1;
// Don't use multi-threaded decoding.
config_.options.use_threads = 0;
}
WEBPImageDecoder::~WEBPImageDecoder() {
TRACE_EVENT0("cobalt::loader::image",
"WEBPImageDecoder::~WEBPImageDecoder()");
DeleteInternalDecoder();
}
size_t WEBPImageDecoder::DecodeChunkInternal(const uint8* data,
size_t input_byte) {
TRACK_MEMORY_SCOPE("Rendering");
TRACE_EVENT0("cobalt::loader::image",
"WEBPImageDecoder::DecodeChunkInternal()");
if (state() == kWaitingForHeader) {
if (!ReadHeader(data, input_byte)) {
return 0;
}
if (!config_.input.has_animation) {
decoded_image_data_ = AllocateImageData(
math::Size(config_.input.width, config_.input.height),
!!config_.input.has_alpha);
if (decoded_image_data_ == NULL) {
return 0;
}
if (!CreateInternalDecoder()) {
return 0;
}
} else {
animated_webp_image_ = new AnimatedWebPImage(
math::Size(config_.input.width, config_.input.height),
!!config_.input.has_alpha, resource_provider());
}
set_state(kReadLines);
}
if (state() == kReadLines) {
if (!config_.input.has_animation) {
// Copies and decodes the next available data. Returns VP8_STATUS_OK when
// the image is successfully decoded. Returns VP8_STATUS_SUSPENDED when
// more data is expected. Returns error in other cases.
VP8StatusCode status = WebPIAppend(internal_decoder_, data, input_byte);
if (status == VP8_STATUS_OK) {
DCHECK(decoded_image_data_);
DCHECK(config_.output.u.RGBA.rgba);
DCHECK_EQ(config_.output.u.RGBA.stride,
decoded_image_data_->GetDescriptor().pitch_in_bytes);
set_state(kDone);
} else if (status != VP8_STATUS_SUSPENDED) {
DLOG(ERROR) << "WebPIAppend error, status code: " << status;
DeleteInternalDecoder();
set_state(kError);
return 0;
}
} else {
animated_webp_image_->AppendChunk(data, input_byte);
}
}
return input_byte;
}
scoped_refptr<Image> WEBPImageDecoder::FinishInternal() {
if (config_.input.has_animation) {
set_state(kDone);
return animated_webp_image_;
}
if (state() != kDone) {
decoded_image_data_.reset();
return NULL;
}
SB_DCHECK(decoded_image_data_);
return CreateStaticImage(std::move(decoded_image_data_));
}
bool WEBPImageDecoder::ReadHeader(const uint8* data, size_t size) {
TRACK_MEMORY_SCOPE("Rendering");
TRACE_EVENT0("cobalt::loader::image", "WEBPImageDecoder::ReadHeader()");
// Retrieve features from the bitstream. The *features structure is filled
// with information gathered from the bitstream.
// Returns VP8_STATUS_OK when the features are successfully retrieved. Returns
// VP8_STATUS_NOT_ENOUGH_DATA when more data is needed to retrieve the
// features from headers. Returns error in other cases.
VP8StatusCode status = WebPGetFeatures(data, size, &config_.input);
if (status == VP8_STATUS_OK) {
return true;
} else if (status == VP8_STATUS_NOT_ENOUGH_DATA) {
// Data is not enough for decoding the header.
return false;
} else {
DLOG(ERROR) << "WebPGetFeatures error, status code: " << status;
set_state(kError);
return false;
}
}
bool WEBPImageDecoder::CreateInternalDecoder() {
TRACK_MEMORY_SCOPE("Rendering");
TRACE_EVENT0("cobalt::loader::image",
"WEBPImageDecoder::CreateInternalDecoder()");
bool has_alpha = !!config_.input.has_alpha;
config_.output.colorspace = pixel_format() == render_tree::kPixelFormatRGBA8
? (has_alpha ? MODE_rgbA : MODE_RGBA)
: (has_alpha ? MODE_bgrA : MODE_BGRA);
auto image_data_descriptor = decoded_image_data_->GetDescriptor();
config_.output.u.RGBA.rgba = decoded_image_data_->GetMemory();
config_.output.u.RGBA.stride = image_data_descriptor.pitch_in_bytes;
config_.output.u.RGBA.size = image_data_descriptor.pitch_in_bytes *
image_data_descriptor.size.height();
#if SB_HAS_QUIRK(GL_MAP_BUFFER_MEMORY_IS_SLOW_TO_READ)
// The webp decoder will allocate a main memory output buffer if it has to
// read from the output buffer.
config_.output.is_external_memory = 2;
#else // SB_HAS_QUIRK(GL_MAP_BUFFER_MEMORY_IS_SLOW_TO_READ)
config_.output.is_external_memory = 1;
#endif // SB_HAS_QUIRK(GL_MAP_BUFFER_MEMORY_IS_SLOW_TO_READ)
// Instantiate a new incremental decoder object with the requested
// configuration.
internal_decoder_ = WebPIDecode(NULL, 0, &config_);
if (internal_decoder_ == NULL) {
DLOG(WARNING) << "Create internal WEBP decoder failed.";
set_state(kError);
return false;
}
return true;
}
void WEBPImageDecoder::DeleteInternalDecoder() {
TRACE_EVENT0("cobalt::loader::image",
"WEBPImageDecoder::DeleteInternalDecoder()");
if (internal_decoder_) {
// Deletes the WebPIDecoder object and associated memory. Must always be
// called if WebPIDecode succeeded.
WebPIDelete(internal_decoder_);
internal_decoder_ = NULL;
WebPFreeDecBuffer(&config_.output);
}
}
} // namespace image
} // namespace loader
} // namespace cobalt