diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-12-10 16:19:40 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-12-10 16:01:50 +0000 |
commit | 51f6c2793adab2d864b3d2b360000ef8db1d3e92 (patch) | |
tree | 835b3b4446b012c75e80177cef9fbe6972cc7dbe /chromium/third_party/blink/renderer/platform/image-decoders | |
parent | 6036726eb981b6c4b42047513b9d3f4ac865daac (diff) | |
download | qtwebengine-chromium-51f6c2793adab2d864b3d2b360000ef8db1d3e92.tar.gz |
BASELINE: Update Chromium to 71.0.3578.93
Change-Id: I6a32086c33670e1b033f8b10e6bf1fd4da1d105d
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/image-decoders')
19 files changed, 1501 insertions, 1609 deletions
diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/DEPS b/chromium/third_party/blink/renderer/platform/image-decoders/DEPS index d6e7af7d420..6fee6fc492e 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/DEPS +++ b/chromium/third_party/blink/renderer/platform/image-decoders/DEPS @@ -7,7 +7,6 @@ include_rules = [ # Dependencies. "+cc/paint/image_animation_count.h", - "+third_party/blink/renderer/platform/histogram.h", "+third_party/blink/renderer/platform/geometry", "+third_party/blink/renderer/platform/graphics", "+third_party/blink/renderer/platform/histogram.h", diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h index db5a170fe66..2bb15ce8d30 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h +++ b/chromium/third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h @@ -119,7 +119,7 @@ class PLATFORM_EXPORT BMPImageReader final { uint32_t bi_clr_used; }; struct RGBTriple { - DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + DISALLOW_NEW(); uint8_t rgb_blue; uint8_t rgb_green; uint8_t rgb_red; diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.cc b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.cc index 860215b58e7..087126ea5ec 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.cc +++ b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.cc @@ -26,261 +26,340 @@ #include "third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h" #include <limits> -#include <memory> -#include "third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h" +#include "third_party/blink/renderer/platform/image-decoders/segment_stream.h" #include "third_party/blink/renderer/platform/wtf/not_found.h" +#include "third_party/skia/include/core/SkImageInfo.h" namespace blink { +namespace { + +ImageFrame::DisposalMethod ConvertDisposalMethod( + SkCodecAnimation::DisposalMethod disposal_method) { + switch (disposal_method) { + case SkCodecAnimation::DisposalMethod::kKeep: + return ImageFrame::kDisposeKeep; + case SkCodecAnimation::DisposalMethod::kRestoreBGColor: + return ImageFrame::kDisposeOverwriteBgcolor; + case SkCodecAnimation::DisposalMethod::kRestorePrevious: + return ImageFrame::kDisposeOverwritePrevious; + default: + return ImageFrame::kDisposeNotSpecified; + } +} + +} // anonymous namespace + GIFImageDecoder::GIFImageDecoder(AlphaOption alpha_option, const ColorBehavior& color_behavior, size_t max_decoded_bytes) : ImageDecoder(alpha_option, ImageDecoder::kDefaultBitDepth, color_behavior, - max_decoded_bytes), - repetition_count_(kAnimationLoopOnce) {} + max_decoded_bytes) {} GIFImageDecoder::~GIFImageDecoder() = default; void GIFImageDecoder::OnSetData(SegmentReader* data) { - if (reader_) - reader_->SetData(data); + if (!data) { + if (segment_stream_) + segment_stream_->SetReader(nullptr); + return; + } + + std::unique_ptr<SegmentStream> segment_stream; + if (!segment_stream_) { + segment_stream = std::make_unique<SegmentStream>(); + segment_stream_ = segment_stream.get(); + } + + segment_stream_->SetReader(data); + + if (!codec_) { + SkCodec::Result codec_creation_result; + codec_ = SkCodec::MakeFromStream(std::move(segment_stream), + &codec_creation_result, nullptr); + switch (codec_creation_result) { + case SkCodec::kSuccess: { + // SkCodec::MakeFromStream will read enough of the image to get the + // image size. + SkImageInfo image_info = codec_->getInfo(); + SetSize(image_info.width(), image_info.height()); + return; + } + case SkCodec::kIncompleteInput: + // |segment_stream_|'s ownership is passed into MakeFromStream. + // It is deleted if MakeFromStream fails. + // If MakeFromStream fails, we set |segment_stream_| to null so + // we aren't pointing to reclaimed memory. + segment_stream_ = nullptr; + return; + default: + SetFailed(); + return; + } + } } int GIFImageDecoder::RepetitionCount() const { + if (!codec_ || segment_stream_->IsCleared()) + return repetition_count_; + + DCHECK(!Failed()); + // This value can arrive at any point in the image data stream. Most GIFs // in the wild declare it near the beginning of the file, so it usually is // set by the time we've decoded the size, but (depending on the GIF and the - // packets sent back by the webserver) not always. If the reader hasn't - // seen a loop count yet, it will return kCLoopCountNotSeen, in which case we - // should default to looping once (the initial value for - // |repetition_count_|). - // - // There are some additional wrinkles here. First, ImageSource::Clear() - // may destroy the reader, making the result from the reader _less_ - // authoritative on future calls if the recreated reader hasn't seen the - // loop count. We don't need to special-case this because in this case the - // new reader will once again return kCLoopCountNotSeen, and we won't - // overwrite the cached correct value. + // packets sent back by the webserver) not always. // - // Second, a GIF might never set a loop count at all, in which case we - // should continue to treat it as a "loop once" animation. We don't need - // special code here either, because in this case we'll never change - // |repetition_count_| from its default value. - // - // Third, we use the same GIFImageReader for counting frames and we might - // see the loop count and then encounter a decoding error which happens - // later in the stream. It is also possible that no frames are in the - // stream. In these cases we should just loop once. - if (IsAllDataReceived() && ParseCompleted() && reader_->ImagesCount() == 1) - repetition_count_ = kAnimationNone; - else if (Failed() || (reader_ && (!reader_->ImagesCount()))) - repetition_count_ = kAnimationLoopOnce; - else if (reader_ && reader_->LoopCount() != kCLoopCountNotSeen) - repetition_count_ = reader_->LoopCount(); + // SkCodec will parse forward in the file if the repetition count has not been + // seen yet. + int repetition_count = codec_->getRepetitionCount(); + + switch (repetition_count) { + case 0: { + // SkCodec returns 0 for both still images and animated images which only + // play once. + if (IsAllDataReceived() && codec_->getFrameCount() == 1) { + repetition_count_ = kAnimationNone; + break; + } + + repetition_count_ = kAnimationLoopOnce; + break; + } + case SkCodec::kRepetitionCountInfinite: + repetition_count_ = kAnimationLoopInfinite; + break; + default: + repetition_count_ = repetition_count; + break; + } + return repetition_count_; } bool GIFImageDecoder::FrameIsReceivedAtIndex(size_t index) const { - return reader_ && (index < reader_->ImagesCount()) && - reader_->FrameContext(index)->IsComplete(); + SkCodec::FrameInfo frame_info; + if (!codec_ || !codec_->getFrameInfo(index, &frame_info)) + return false; + return frame_info.fFullyReceived; } TimeDelta GIFImageDecoder::FrameDurationAtIndex(size_t index) const { - return (reader_ && (index < reader_->ImagesCount()) && - reader_->FrameContext(index)->IsHeaderDefined()) - ? TimeDelta::FromMilliseconds( - reader_->FrameContext(index)->DelayTime()) - : TimeDelta(); + if (index < frame_buffer_cache_.size()) + return frame_buffer_cache_[index].Duration(); + return TimeDelta(); } bool GIFImageDecoder::SetFailed() { - reader_.reset(); + segment_stream_ = nullptr; + codec_.reset(); return ImageDecoder::SetFailed(); } -bool GIFImageDecoder::HaveDecodedRow(size_t frame_index, - GIFRow::const_iterator row_begin, - size_t width, - size_t row_number, - unsigned repeat_count, - bool write_transparent_pixels) { - const GIFFrameContext* frame_context = reader_->FrameContext(frame_index); - // The pixel data and coordinates supplied to us are relative to the frame's - // origin within the entire image size, i.e. - // (frameC_context->xOffset, frame_context->yOffset). There is no guarantee - // that width == (size().width() - frame_context->xOffset), so - // we must ensure we don't run off the end of either the source data or the - // row's X-coordinates. - const int x_begin = frame_context->XOffset(); - const int y_begin = frame_context->YOffset() + row_number; - const int x_end = std::min(static_cast<int>(frame_context->XOffset() + width), - Size().Width()); - const int y_end = std::min( - static_cast<int>(frame_context->YOffset() + row_number + repeat_count), - Size().Height()); - if (!width || (x_begin < 0) || (y_begin < 0) || (x_end <= x_begin) || - (y_end <= y_begin)) - return true; - - const GIFColorMap::Table& color_table = - frame_context->LocalColorMap().IsDefined() - ? frame_context->LocalColorMap().GetTable() - : reader_->GlobalColorMap().GetTable(); - - if (color_table.IsEmpty()) - return true; - - GIFColorMap::Table::const_iterator color_table_iter = color_table.begin(); - - // Initialize the frame if necessary. - ImageFrame& buffer = frame_buffer_cache_[frame_index]; - if (!InitFrameBuffer(frame_index)) - return false; - - const size_t transparent_pixel = frame_context->TransparentPixel(); - GIFRow::const_iterator row_end = row_begin + (x_end - x_begin); - ImageFrame::PixelData* current_address = buffer.GetAddr(x_begin, y_begin); - - // We may or may not need to write transparent pixels to the buffer. - // If we're compositing against a previous image, it's wrong, and if - // we're writing atop a cleared, fully transparent buffer, it's - // unnecessary; but if we're decoding an interlaced gif and - // displaying it "Haeberli"-style, we must write these for passes - // beyond the first, or the initial passes will "show through" the - // later ones. - // - // The loops below are almost identical. One writes a transparent pixel - // and one doesn't based on the value of |write_transparent_pixels|. - // The condition check is taken out of the loop to enhance performance. - // This optimization reduces decoding time by about 15% for a 3MB image. - if (write_transparent_pixels) { - for (; row_begin != row_end; ++row_begin, ++current_address) { - const size_t source_value = *row_begin; - if ((source_value != transparent_pixel) && - (source_value < color_table.size())) { - *current_address = color_table_iter[source_value]; - } else { - *current_address = 0; - current_buffer_saw_alpha_ = true; - } - } - } else { - for (; row_begin != row_end; ++row_begin, ++current_address) { - const size_t source_value = *row_begin; - if ((source_value != transparent_pixel) && - (source_value < color_table.size())) - *current_address = color_table_iter[source_value]; - else - current_buffer_saw_alpha_ = true; +size_t GIFImageDecoder::ClearCacheExceptFrame(size_t index) { + if (frame_buffer_cache_.size() <= 1) + return 0; + + // SkCodec attempts to report the earliest possible required frame. But it is + // possible that frame has been evicted. A later frame which could also + // be used as the required frame may still be cached. Try to preserve a frame + // that is still cached. + size_t index2 = kNotFound; + if (index < frame_buffer_cache_.size()) { + const ImageFrame& frame = frame_buffer_cache_[index]; + if (frame.RequiredPreviousFrameIndex() != kNotFound && + (!FrameStatusSufficientForSuccessors(index) || + frame.GetDisposalMethod() == ImageFrame::kDisposeOverwritePrevious)) { + index2 = GetViableReferenceFrameIndex(index); } } - // Tell the frame to copy the row data if need be. - if (repeat_count > 1) - buffer.CopyRowNTimes(x_begin, x_end, y_begin, y_end); - - buffer.SetPixelsChanged(true); - return true; + return ClearCacheExceptTwoFrames(index, index2); } -bool GIFImageDecoder::ParseCompleted() const { - return reader_ && reader_->ParseCompleted(); -} - -bool GIFImageDecoder::FrameComplete(size_t frame_index) { - // Initialize the frame if necessary. Some GIFs insert do-nothing frames, - // in which case we never reach HaveDecodedRow() before getting here. - if (!InitFrameBuffer(frame_index)) - return SetFailed(); - - if (!current_buffer_saw_alpha_) - CorrectAlphaWhenFrameBufferSawNoAlpha(frame_index); - - frame_buffer_cache_[frame_index].SetStatus(ImageFrame::kFrameComplete); +size_t GIFImageDecoder::DecodeFrameCount() { + if (!codec_ || segment_stream_->IsCleared()) + return frame_buffer_cache_.size(); - return true; + return codec_->getFrameCount(); } -void GIFImageDecoder::ClearFrameBuffer(size_t frame_index) { - if (reader_ && frame_buffer_cache_[frame_index].GetStatus() == - ImageFrame::kFramePartial) { - // Reset the state of the partial frame in the reader so that the frame - // can be decoded again when requested. - reader_->ClearDecodeState(frame_index); +void GIFImageDecoder::InitializeNewFrame(size_t index) { + DCHECK(codec_); + + ImageFrame& frame = frame_buffer_cache_[index]; + // SkCodec does not inform us if only a portion of the image was updated in + // the current frame. Because of this, rather than correctly filling in the + // frame rect, we set the frame rect to be the image's full size. + // The original frame rect is not used, anyway. + IntSize full_image_size = Size(); + frame.SetOriginalFrameRect(IntRect(IntPoint(), full_image_size)); + + SkCodec::FrameInfo frame_info; + bool frame_info_received = codec_->getFrameInfo(index, &frame_info); + DCHECK(frame_info_received); + frame.SetDuration(TimeDelta::FromMilliseconds(frame_info.fDuration)); + size_t required_previous_frame_index; + if (frame_info.fRequiredFrame == SkCodec::kNoFrame) { + required_previous_frame_index = kNotFound; + } else { + required_previous_frame_index = + static_cast<size_t>(frame_info.fRequiredFrame); } - ImageDecoder::ClearFrameBuffer(frame_index); + frame.SetRequiredPreviousFrameIndex(required_previous_frame_index); + frame.SetDisposalMethod(ConvertDisposalMethod(frame_info.fDisposalMethod)); } -size_t GIFImageDecoder::DecodeFrameCount() { - Parse(kGIFFrameCountQuery); - // If decoding fails, |reader_| will have been destroyed. Instead of - // returning 0 in this case, return the existing number of frames. This way - // if we get halfway through the image before decoding fails, we won't - // suddenly start reporting that the image has zero frames. - return Failed() ? frame_buffer_cache_.size() : reader_->ImagesCount(); -} +void GIFImageDecoder::Decode(size_t index) { + if (!codec_ || segment_stream_->IsCleared()) + return; -void GIFImageDecoder::InitializeNewFrame(size_t index) { - ImageFrame* buffer = &frame_buffer_cache_[index]; - const GIFFrameContext* frame_context = reader_->FrameContext(index); - buffer->SetOriginalFrameRect( - Intersection(frame_context->FrameRect(), IntRect(IntPoint(), Size()))); - buffer->SetDuration(TimeDelta::FromMilliseconds(frame_context->DelayTime())); - buffer->SetDisposalMethod(frame_context->GetDisposalMethod()); - buffer->SetRequiredPreviousFrameIndex( - FindRequiredPreviousFrame(index, false)); -} + DCHECK(!Failed()); -void GIFImageDecoder::Decode(size_t index) { - Parse(kGIFFrameCountQuery); + DCHECK_LT(index, frame_buffer_cache_.size()); - if (Failed()) + ImageFrame& frame = frame_buffer_cache_[index]; + if (frame.GetStatus() == ImageFrame::kFrameComplete) return; UpdateAggressivePurging(index); - Vector<size_t> frames_to_decode = FindFramesToDecode(index); - for (auto i = frames_to_decode.rbegin(); i != frames_to_decode.rend(); ++i) { - if (!reader_->Decode(*i)) { - SetFailed(); - return; - } + if (frame.GetStatus() == ImageFrame::kFrameEmpty) { + size_t required_previous_frame_index = frame.RequiredPreviousFrameIndex(); + if (required_previous_frame_index == kNotFound) { + frame.AllocatePixelData(Size().Width(), Size().Height(), + ColorSpaceForSkImages()); + frame.ZeroFillPixelData(); + prior_frame_ = SkCodec::kNoFrame; + } else { + size_t previous_frame_index = GetViableReferenceFrameIndex(index); + if (previous_frame_index == kNotFound) { + previous_frame_index = required_previous_frame_index; + Decode(previous_frame_index); + if (Failed()) { + return; + } + } - // If this returns false, we need more data to continue decoding. - if (!PostDecodeProcessing(*i)) - break; + // We try to reuse |previous_frame| as starting state to avoid copying. + // If CanReusePreviousFrameBuffer returns false, we must copy the data + // since |previous_frame| is necessary to decode this or later frames. + // In that case copy the data instead. + ImageFrame& previous_frame = frame_buffer_cache_[previous_frame_index]; + if ((!CanReusePreviousFrameBuffer(index) || + !frame.TakeBitmapDataIfWritable(&previous_frame)) && + !frame.CopyBitmapData(previous_frame)) { + SetFailed(); + return; + } + prior_frame_ = previous_frame_index; + } } - // It is also a fatal error if all data is received and we have decoded all - // frames available but the file is truncated. - if (index >= frame_buffer_cache_.size() - 1 && IsAllDataReceived() && - reader_ && !reader_->ParseCompleted()) - SetFailed(); -} + if (frame.GetStatus() == ImageFrame::kFrameInitialized) { + SkCodec::FrameInfo frame_info; + bool frame_info_received = codec_->getFrameInfo(index, &frame_info); + DCHECK(frame_info_received); -void GIFImageDecoder::Parse(GIFParseQuery query) { - if (Failed()) - return; + SkAlphaType alpha_type = kOpaque_SkAlphaType; + if (frame_info.fAlphaType != kOpaque_SkAlphaType) { + if (premultiply_alpha_) { + alpha_type = kPremul_SkAlphaType; + } else { + alpha_type = kUnpremul_SkAlphaType; + } + } - if (!reader_) { - reader_ = std::make_unique<GIFImageReader>(this); - reader_->SetData(data_); + SkImageInfo image_info = codec_->getInfo() + .makeColorType(kN32_SkColorType) + .makeColorSpace(ColorSpaceForSkImages()) + .makeAlphaType(alpha_type); + + SkCodec::Options options; + options.fFrameIndex = index; + options.fPriorFrame = prior_frame_; + options.fZeroInitialized = SkCodec::kNo_ZeroInitialized; + + SkCodec::Result start_incremental_decode_result = + codec_->startIncrementalDecode(image_info, frame.Bitmap().getPixels(), + frame.Bitmap().rowBytes(), &options); + switch (start_incremental_decode_result) { + case SkCodec::kSuccess: + break; + case SkCodec::kIncompleteInput: + return; + default: + SetFailed(); + return; + } + frame.SetStatus(ImageFrame::kFramePartial); } - if (!reader_->Parse(query)) - SetFailed(); -} - -void GIFImageDecoder::OnInitFrameBuffer(size_t frame_index) { - current_buffer_saw_alpha_ = false; + SkCodec::Result incremental_decode_result = codec_->incrementalDecode(); + switch (incremental_decode_result) { + case SkCodec::kSuccess: { + SkCodec::FrameInfo frame_info; + bool frame_info_received = codec_->getFrameInfo(index, &frame_info); + DCHECK(frame_info_received); + frame.SetHasAlpha(frame_info.fAlphaType != + SkAlphaType::kOpaque_SkAlphaType); + frame.SetPixelsChanged(true); + frame.SetStatus(ImageFrame::kFrameComplete); + PostDecodeProcessing(index); + break; + } + case SkCodec::kIncompleteInput: + frame.SetPixelsChanged(true); + if (FrameIsReceivedAtIndex(index) || IsAllDataReceived()) { + SetFailed(); + } + break; + default: + frame.SetPixelsChanged(true); + SetFailed(); + break; + } } bool GIFImageDecoder::CanReusePreviousFrameBuffer(size_t frame_index) const { - DCHECK(frame_index < frame_buffer_cache_.size()); + DCHECK_LT(frame_index, frame_buffer_cache_.size()); return frame_buffer_cache_[frame_index].GetDisposalMethod() != ImageFrame::kDisposeOverwritePrevious; } +size_t GIFImageDecoder::GetViableReferenceFrameIndex( + size_t dependent_index) const { + DCHECK_LT(dependent_index, frame_buffer_cache_.size()); + + size_t required_previous_frame_index = + frame_buffer_cache_[dependent_index].RequiredPreviousFrameIndex(); + + // Any frame in the range [|required_previous_frame_index|, |dependent_index|) + // which has a disposal method other than kRestorePrevious can be provided as + // the prior frame to SkCodec. + // + // SkCodec sets SkCodec::FrameInfo::fRequiredFrame to the earliest frame which + // can be used. This might come up when several frames update the same + // subregion. If that same subregion is about to be overwritten, it doesn't + // matter which frame in that chain is provided. + DCHECK_NE(required_previous_frame_index, kNotFound); + // Loop backwards because the frames most likely to be in cache are the most + // recent. + for (size_t i = dependent_index - 1; i != required_previous_frame_index; + i--) { + const ImageFrame& frame = frame_buffer_cache_[i]; + + if (frame.GetDisposalMethod() == ImageFrame::kDisposeOverwritePrevious) + continue; + + if (frame.GetStatus() == ImageFrame::kFrameComplete) { + return i; + } + } + + return kNotFound; +} + } // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h index c94f7eaae0e..1b8d5b11b1e 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h +++ b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h @@ -30,12 +30,11 @@ #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" #include "third_party/blink/renderer/platform/wtf/noncopyable.h" #include "third_party/blink/renderer/platform/wtf/time.h" +#include "third_party/skia/include/codec/SkCodec.h" namespace blink { -class GIFImageReader; - -using GIFRow = Vector<unsigned char>; +class SegmentStream; // This class decodes the GIF image format. class PLATFORM_EXPORT GIFImageDecoder final : public ImageDecoder { @@ -45,56 +44,43 @@ class PLATFORM_EXPORT GIFImageDecoder final : public ImageDecoder { GIFImageDecoder(AlphaOption, const ColorBehavior&, size_t max_decoded_bytes); ~GIFImageDecoder() override; - enum GIFParseQuery { kGIFSizeQuery, kGIFFrameCountQuery }; - // ImageDecoder: String FilenameExtension() const override { return "gif"; } void OnSetData(SegmentReader* data) override; int RepetitionCount() const override; bool FrameIsReceivedAtIndex(size_t) const override; TimeDelta FrameDurationAtIndex(size_t) const override; - // CAUTION: SetFailed() deletes |reader_|. Be careful to avoid - // accessing deleted memory, especially when calling this from inside - // GIFImageReader! + // CAUTION: SetFailed() deletes |codec_|. Be careful to avoid + // accessing deleted memory. bool SetFailed() override; - // Callbacks from the GIF reader. - bool HaveDecodedRow(size_t frame_index, - GIFRow::const_iterator row_begin, - size_t width, - size_t row_number, - unsigned repeat_count, - bool write_transparent_pixels); - bool FrameComplete(size_t frame_index); - - // For testing. - bool ParseCompleted() const; + size_t ClearCacheExceptFrame(size_t) override; private: // ImageDecoder: - void ClearFrameBuffer(size_t frame_index) override; - void DecodeSize() override { Parse(kGIFSizeQuery); } + void DecodeSize() override {} size_t DecodeFrameCount() override; void InitializeNewFrame(size_t) override; void Decode(size_t) override; - - // Parses as much as is needed to answer the query, ignoring bitmap - // data. If parsing fails, sets the "decode failure" flag. - void Parse(GIFParseQuery); - - // Reset the alpha tracker for this frame. Before calling this method, the - // caller must verify that the frame exists. - void OnInitFrameBuffer(size_t) override; - // When the disposal method of the frame is DisposeOverWritePrevious, the - // next frame will use the previous frame's buffer as its starting state, so + // next frame will use a previous frame's buffer as its starting state, so // we can't take over the data in that case. Before calling this method, the // caller must verify that the frame exists. bool CanReusePreviousFrameBuffer(size_t) const override; - bool current_buffer_saw_alpha_; - mutable int repetition_count_; - std::unique_ptr<GIFImageReader> reader_; + // When a frame depends on a previous frame's content, there is a list of + // candidate reference frames. This function will find a previous frame from + // that list which satisfies the requirements of being a reference frame + // (kFrameComplete, not kDisposeOverwritePrevious). + // If no frame is found, it returns kNotFound. + size_t GetViableReferenceFrameIndex(size_t) const; + + std::unique_ptr<SkCodec> codec_; + // |codec_| owns the SegmentStream, but we need access to it to append more + // data as it arrives. + SegmentStream* segment_stream_ = nullptr; + mutable int repetition_count_ = kAnimationLoopOnce; + int prior_frame_ = SkCodec::kNoFrame; }; } // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder_test.cc index 289241fba6f..414dc305c23 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder_test.cc +++ b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder_test.cc @@ -57,16 +57,7 @@ void TestRepetitionCount(const char* dir, scoped_refptr<SharedBuffer> data = ReadFile(dir, file); ASSERT_TRUE(data.get()); decoder->SetData(data.get(), true); - EXPECT_EQ(kAnimationLoopOnce, - decoder->RepetitionCount()); // Default value before decode. - - for (size_t i = 0; i < decoder->FrameCount(); ++i) { - ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(i); - EXPECT_EQ(ImageFrame::kFrameComplete, frame->GetStatus()); - } - - EXPECT_EQ(expected_repetition_count, - decoder->RepetitionCount()); // Expected value after decode. + EXPECT_EQ(expected_repetition_count, decoder->RepetitionCount()); } } // anonymous namespace @@ -78,7 +69,6 @@ TEST(GIFImageDecoderTest, decodeTwoFrames) { ReadFile(kLayoutTestResourcesDir, "animated.gif"); ASSERT_TRUE(data.get()); decoder->SetData(data.get(), true); - EXPECT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); uint32_t generation_id0 = frame->Bitmap().getGenerationID(); @@ -125,7 +115,6 @@ TEST(GIFImageDecoderTest, parseAndDecode) { ReadFile(kLayoutTestResourcesDir, "animated.gif"); ASSERT_TRUE(data.get()); decoder->SetData(data.get(), true); - EXPECT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); // This call will parse the entire file. EXPECT_EQ(2u, decoder->FrameCount()); @@ -345,10 +334,13 @@ TEST(GIFImageDecoderTest, invalidDisposalMethod) { EXPECT_EQ(2u, decoder->FrameCount()); // Disposal method 4 is converted to ImageFrame::DisposeOverwritePrevious. + // This is because some specs say method 3 is "overwrite previous", while + // others say setting the third bit (i.e. method 4) is. EXPECT_EQ(ImageFrame::kDisposeOverwritePrevious, decoder->DecodeFrameBufferAtIndex(0)->GetDisposalMethod()); - // Disposal method 5 is ignored. - EXPECT_EQ(ImageFrame::kDisposeNotSpecified, + // Unknown disposal methods (5 in this case) are converted to + // ImageFrame::DisposeKeep. + EXPECT_EQ(ImageFrame::kDisposeKeep, decoder->DecodeFrameBufferAtIndex(1)->GetDisposalMethod()); } @@ -384,6 +376,28 @@ TEST(GIFImageDecoderTest, verifyRepetitionCount) { TestRepetitionCount(kDecodersTestingDir, "radient.gif", kAnimationNone); } +TEST(GIFImageDecoderTest, repetitionCountChangesWhenSeen) { + scoped_refptr<SharedBuffer> full_data_buffer = + ReadFile(kLayoutTestResourcesDir, "animated-10color.gif"); + ASSERT_TRUE(full_data_buffer.get()); + const Vector<char> full_data = full_data_buffer->CopyAs<Vector<char>>(); + + // This size must be before the repetition count is encountered in the file. + const size_t kTruncatedSize = 60; + ASSERT_TRUE(kTruncatedSize < full_data.size()); + scoped_refptr<SharedBuffer> partial_data = + SharedBuffer::Create(full_data.data(), kTruncatedSize); + + std::unique_ptr<ImageDecoder> decoder = std::make_unique<GIFImageDecoder>( + ImageDecoder::kAlphaPremultiplied, ColorBehavior::TransformToSRGB(), + ImageDecoder::kNoDecodedImageByteLimit); + + decoder->SetData(partial_data.get(), false); + ASSERT_EQ(kAnimationLoopOnce, decoder->RepetitionCount()); + decoder->SetData(full_data_buffer.get(), true); + ASSERT_EQ(kAnimationLoopInfinite, decoder->RepetitionCount()); +} + TEST(GIFImageDecoderTest, bitmapAlphaType) { scoped_refptr<SharedBuffer> full_data_buffer = ReadFile(kDecodersTestingDir, "radient.gif"); @@ -416,11 +430,11 @@ TEST(GIFImageDecoderTest, bitmapAlphaType) { ImageFrame* premul_frame = premul_decoder->DecodeFrameBufferAtIndex(0); EXPECT_TRUE(premul_frame && premul_frame->GetStatus() != ImageFrame::kFrameComplete); - EXPECT_EQ(premul_frame->Bitmap().alphaType(), kPremul_SkAlphaType); + EXPECT_EQ(kPremul_SkAlphaType, premul_frame->Bitmap().alphaType()); ImageFrame* unpremul_frame = unpremul_decoder->DecodeFrameBufferAtIndex(0); EXPECT_TRUE(unpremul_frame && unpremul_frame->GetStatus() != ImageFrame::kFrameComplete); - EXPECT_EQ(unpremul_frame->Bitmap().alphaType(), kUnpremul_SkAlphaType); + EXPECT_EQ(kUnpremul_SkAlphaType, unpremul_frame->Bitmap().alphaType()); // Fully decoded frame => the frame alpha type is known (opaque). premul_decoder->SetData(full_data_buffer.get(), true); @@ -430,11 +444,11 @@ TEST(GIFImageDecoderTest, bitmapAlphaType) { premul_frame = premul_decoder->DecodeFrameBufferAtIndex(0); EXPECT_TRUE(premul_frame && premul_frame->GetStatus() == ImageFrame::kFrameComplete); - EXPECT_EQ(premul_frame->Bitmap().alphaType(), kOpaque_SkAlphaType); + EXPECT_EQ(kOpaque_SkAlphaType, premul_frame->Bitmap().alphaType()); unpremul_frame = unpremul_decoder->DecodeFrameBufferAtIndex(0); EXPECT_TRUE(unpremul_frame && unpremul_frame->GetStatus() == ImageFrame::kFrameComplete); - EXPECT_EQ(unpremul_frame->Bitmap().alphaType(), kOpaque_SkAlphaType); + EXPECT_EQ(kOpaque_SkAlphaType, unpremul_frame->Bitmap().alphaType()); } namespace { diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.cc b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.cc deleted file mode 100644 index 908a76fc7ba..00000000000 --- a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.cc +++ /dev/null @@ -1,902 +0,0 @@ -/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (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.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Chris Saari <saari@netscape.com> - * Apple Computer - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -/* -The Graphics Interchange Format(c) is the copyright property of CompuServe -Incorporated. Only CompuServe Incorporated is authorized to define, redefine, -enhance, alter, modify or change in any way the definition of the format. - -CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free -license for the use of the Graphics Interchange Format(sm) in computer -software; computer software utilizing GIF(sm) must acknowledge ownership of the -Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in -User and Technical Documentation. Computer software utilizing GIF, which is -distributed or may be distributed without User or Technical Documentation must -display to the screen or printer a message acknowledging ownership of the -Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in -this case, the acknowledgement may be displayed in an opening screen or leading -banner, or a closing screen or trailing banner. A message such as the following -may be used: - - "The Graphics Interchange Format(c) is the Copyright property of - CompuServe Incorporated. GIF(sm) is a Service Mark property of - CompuServe Incorporated." - -For further information, please contact : - - CompuServe Incorporated - Graphics Technology Department - 5000 Arlington Center Boulevard - Columbus, Ohio 43220 - U. S. A. - -CompuServe Incorporated maintains a mailing list with all those individuals and -organizations who wish to receive copies of this document when it is corrected -or revised. This service is offered free of charge; please provide us with your -mailing address. -*/ - -#include "third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h" - -#include <string.h> -#include <algorithm> - -#include "base/memory/ptr_util.h" -#include "third_party/blink/renderer/platform/image-decoders/fast_shared_buffer_reader.h" - -namespace blink { - -namespace { - -static constexpr unsigned kMaxColors = 256u; -static constexpr int kBytesPerColormapEntry = 3; - -} // namespace - -// GETN(n, s) requests at least 'n' bytes available from 'q', at start of state -// 's'. -// -// Note: the hold will never need to be bigger than 256 bytes, as each GIF block -// (except colormaps) can never be bigger than 256 bytes. Colormaps are directly -// copied in the resp. global_colormap or dynamically allocated local_colormap, -// so a fixed buffer in GIFImageReader is good enough. This buffer is only -// needed to copy left-over data from one GifWrite call to the next. -#define GETN(n, s) \ - do { \ - bytes_to_consume_ = (n); \ - state_ = (s); \ - } while (0) - -// Get a 16-bit value stored in little-endian format. -#define GETINT16(p) ((p)[1] << 8 | (p)[0]) - -// Send the data to the display front-end. -bool GIFLZWContext::OutputRow(GIFRow::const_iterator row_begin) { - int drow_start = irow; - int drow_end = irow; - - // Haeberli-inspired hack for interlaced GIFs: Replicate lines while - // displaying to diminish the "venetian-blind" effect as the image is - // loaded. Adjust pixel vertical positions to avoid the appearance of the - // image crawling up the screen as successive passes are drawn. - if (frame_context_->ProgressiveDisplay() && frame_context_->Interlaced() && - ipass < 4) { - unsigned row_dup = 0; - unsigned row_shift = 0; - - switch (ipass) { - case 1: - row_dup = 7; - row_shift = 3; - break; - case 2: - row_dup = 3; - row_shift = 1; - break; - case 3: - row_dup = 1; - row_shift = 0; - break; - default: - break; - } - - drow_start -= row_shift; - drow_end = drow_start + row_dup; - - // Extend if bottom edge isn't covered because of the shift upward. - if (((frame_context_->Height() - 1) - drow_end) <= row_shift) - drow_end = frame_context_->Height() - 1; - - // Clamp first and last rows to upper and lower edge of image. - if (drow_start < 0) - drow_start = 0; - - if ((unsigned)drow_end >= frame_context_->Height()) - drow_end = frame_context_->Height() - 1; - } - - // Protect against too much image data. - if ((unsigned)drow_start >= frame_context_->Height()) - return true; - - // CALLBACK: Let the client know we have decoded a row. - if (!client_->HaveDecodedRow(frame_context_->FrameId(), row_begin, - frame_context_->Width(), drow_start, - drow_end - drow_start + 1, - frame_context_->ProgressiveDisplay() && - frame_context_->Interlaced() && ipass > 1)) - return false; - - if (!frame_context_->Interlaced()) { - irow++; - } else { - do { - switch (ipass) { - case 1: - irow += 8; - if (irow >= frame_context_->Height()) { - ipass++; - irow = 4; - } - break; - - case 2: - irow += 8; - if (irow >= frame_context_->Height()) { - ipass++; - irow = 2; - } - break; - - case 3: - irow += 4; - if (irow >= frame_context_->Height()) { - ipass++; - irow = 1; - } - break; - - case 4: - irow += 2; - if (irow >= frame_context_->Height()) { - ipass++; - irow = 0; - } - break; - - default: - break; - } - } while (irow > (frame_context_->Height() - 1)); - } - return true; -} - -// Performs Lempel-Ziv-Welch decoding. Returns whether decoding was successful. -// If successful, the block will have been completely consumed and/or -// rowsRemaining will be 0. -bool GIFLZWContext::DoLZW(const unsigned char* block, size_t bytes_in_block) { - const size_t width = frame_context_->Width(); - - if (row_iter == row_buffer.end()) - return true; - - for (const unsigned char* ch = block; bytes_in_block-- > 0; ch++) { - // Feed the next byte into the decoder's 32-bit input buffer. - datum += ((int)*ch) << bits; - bits += 8; - - // Check for underflow of decoder's 32-bit input buffer. - while (bits >= codesize) { - // Get the leading variable-length symbol from the data stream. - int code = datum & codemask; - datum >>= codesize; - bits -= codesize; - - // Reset the dictionary to its original state, if requested. - if (code == clear_code) { - codesize = frame_context_->DataSize() + 1; - codemask = (1 << codesize) - 1; - avail = clear_code + 2; - oldcode = -1; - continue; - } - - // Check for explicit end-of-stream code. - if (code == (clear_code + 1)) { - // end-of-stream should only appear after all image data. - if (!rows_remaining) - return true; - return false; - } - - const int temp_code = code; - unsigned short code_length = 0; - if (code < avail) { - // This is a pre-existing code, so we already know what it - // encodes. - code_length = suffix_length[code]; - row_iter += code_length; - } else if (code == avail && oldcode != -1) { - // This is a new code just being added to the dictionary. - // It must encode the contents of the previous code, plus - // the first character of the previous code again. - code_length = suffix_length[oldcode] + 1; - row_iter += code_length; - *--row_iter = firstchar; - code = oldcode; - } else { - // This is an invalid code. The dictionary is just initialized - // and the code is incomplete. We don't know how to handle - // this case. - return false; - } - - while (code >= clear_code) { - *--row_iter = suffix[code]; - code = prefix[code]; - } - - *--row_iter = firstchar = suffix[code]; - - // Define a new codeword in the dictionary as long as we've read - // more than one value from the stream. - if (avail < kMaxDictionaryEntries && oldcode != -1) { - prefix[avail] = oldcode; - suffix[avail] = firstchar; - suffix_length[avail] = suffix_length[oldcode] + 1; - ++avail; - - // If we've used up all the codewords of a given length - // increase the length of codewords by one bit, but don't - // exceed the specified maximum codeword size. - if (!(avail & codemask) && avail < kMaxDictionaryEntries) { - ++codesize; - codemask += avail; - } - } - oldcode = temp_code; - row_iter += code_length; - - // Output as many rows as possible. - GIFRow::iterator row_begin = row_buffer.begin(); - for (; row_begin + width <= row_iter; row_begin += width) { - if (!OutputRow(row_begin)) - return false; - rows_remaining--; - if (!rows_remaining) - return true; - } - - if (row_begin != row_buffer.begin()) { - // Move the remaining bytes to the beginning of the buffer. - const size_t bytes_to_copy = row_iter - row_begin; - memcpy(row_buffer.begin(), row_begin, bytes_to_copy); - row_iter = row_buffer.begin() + bytes_to_copy; - } - } - } - return true; -} - -void GIFColorMap::BuildTable(FastSharedBufferReader* reader) { - if (!is_defined_ || !table_.IsEmpty()) - return; - - CHECK_LE(position_ + colors_ * kBytesPerColormapEntry, reader->size()); - DCHECK_LE(colors_, kMaxColors); - char buffer[kMaxColors * kBytesPerColormapEntry]; - const unsigned char* src_colormap = - reinterpret_cast<const unsigned char*>(reader->GetConsecutiveData( - position_, colors_ * kBytesPerColormapEntry, buffer)); - table_.resize(colors_); - for (Table::iterator iter = table_.begin(); iter != table_.end(); ++iter) { - *iter = SkPackARGB32NoCheck(255, src_colormap[0], src_colormap[1], - src_colormap[2]); - src_colormap += kBytesPerColormapEntry; - } -} - -// Decodes this frame. |frameDecoded| will be set to true if the entire frame is -// decoded. Returns true if decoding progressed further than before without -// error, or there is insufficient new data to decode further. Otherwise, a -// decoding error occurred; returns false in this case. -bool GIFFrameContext::Decode(FastSharedBufferReader* reader, - GIFImageDecoder* client, - bool* frame_decoded) { - local_color_map_.BuildTable(reader); - - *frame_decoded = false; - if (!lzw_context_) { - // Wait for more data to properly initialize GIFLZWContext. - if (!IsDataSizeDefined() || !IsHeaderDefined()) - return true; - - lzw_context_ = std::make_unique<GIFLZWContext>(client, this); - if (!lzw_context_->PrepareToDecode()) { - lzw_context_.reset(); - return false; - } - - current_lzw_block_ = 0; - } - - // Some bad GIFs have extra blocks beyond the last row, which we don't want to - // decode. - while (current_lzw_block_ < lzw_blocks_.size() && - lzw_context_->HasRemainingRows()) { - size_t block_position = lzw_blocks_[current_lzw_block_].block_position; - size_t block_size = lzw_blocks_[current_lzw_block_].block_size; - if (block_position + block_size > reader->size()) - return false; - - while (block_size) { - const char* segment = nullptr; - size_t segment_length = reader->GetSomeData(segment, block_position); - size_t decode_size = std::min(segment_length, block_size); - if (!lzw_context_->DoLZW(reinterpret_cast<const unsigned char*>(segment), - decode_size)) - return false; - block_position += decode_size; - block_size -= decode_size; - } - ++current_lzw_block_; - } - - // If this frame is data complete then the previous loop must have completely - // decoded all LZW blocks. - // There will be no more decoding for this frame so it's time to cleanup. - if (IsComplete()) { - *frame_decoded = true; - lzw_context_.reset(); - } - return true; -} - -// Decodes a frame using GIFFrameContext:decode(). Returns true if decoding has -// progressed, or false if an error has occurred. -bool GIFImageReader::Decode(size_t frame_index) { - FastSharedBufferReader reader(data_); - global_color_map_.BuildTable(&reader); - - bool frame_decoded = false; - GIFFrameContext* current_frame = frames_[frame_index].get(); - - return current_frame->Decode(&reader, client_, &frame_decoded) && - (!frame_decoded || client_->FrameComplete(frame_index)); -} - -bool GIFImageReader::Parse(GIFImageDecoder::GIFParseQuery query) { - if (bytes_read_ >= data_->size()) { - // This data has already been parsed. For example, in deferred - // decoding, a DecodingImageGenerator with more data may have already - // used this same ImageDecoder to decode. This can happen if two - // SkImages created by a DeferredImageDecoder are drawn/prerolled - // out of order (with respect to how much data they had at creation - // time). - return !client_->Failed(); - } - - return ParseData(bytes_read_, data_->size() - bytes_read_, query); -} - -// Parse incoming GIF data stream into internal data structures. -// Return true if parsing has progressed or there is not enough data. -// Return false if a fatal error is encountered. -bool GIFImageReader::ParseData(size_t data_position, - size_t len, - GIFImageDecoder::GIFParseQuery query) { - if (!len) { - // No new data has come in since the last call, just ignore this call. - return true; - } - - if (len < bytes_to_consume_) - return true; - - FastSharedBufferReader reader(data_); - - // A read buffer of 16 bytes is enough to accomodate all possible reads for - // parsing. - char read_buffer[16]; - - // Read as many components from |m_data| as possible. At the beginning of each - // iteration, |dataPosition| is advanced by m_bytesToConsume to point to the - // next component. |len| is decremented accordingly. - while (len >= bytes_to_consume_) { - const size_t current_component_position = data_position; - - // Mark the current component as consumed. Note that currentComponent will - // remain pointed at this component until the next loop iteration. - data_position += bytes_to_consume_; - len -= bytes_to_consume_; - - switch (state_) { - case GIFLZW: - DCHECK(!frames_.IsEmpty()); - // m_bytesToConsume is the current component size because it hasn't been - // updated. - frames_.back()->AddLzwBlock(current_component_position, - bytes_to_consume_); - GETN(1, kGIFSubBlock); - break; - - case kGIFLZWStart: { - DCHECK(!frames_.IsEmpty()); - frames_.back()->SetDataSize(static_cast<unsigned char>( - reader.GetOneByte(current_component_position))); - GETN(1, kGIFSubBlock); - break; - } - - case kGIFType: { - const char* current_component = reader.GetConsecutiveData( - current_component_position, 6, read_buffer); - - // All GIF files begin with "GIF87a" or "GIF89a". - if (!memcmp(current_component, "GIF89a", 6)) - version_ = 89; - else if (!memcmp(current_component, "GIF87a", 6)) - version_ = 87; - else - return false; - GETN(7, kGIFGlobalHeader); - break; - } - - case kGIFGlobalHeader: { - const unsigned char* current_component = - reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( - current_component_position, 5, read_buffer)); - - // This is the height and width of the "screen" or frame into which - // images are rendered. The individual images can be smaller than - // the screen size and located with an origin anywhere within the - // screen. - // Note that we don't inform the client of the size yet, as it might - // change after we read the first frame's image header. - screen_width_ = GETINT16(current_component); - screen_height_ = GETINT16(current_component + 2); - - const size_t global_color_map_colors = 2 - << (current_component[4] & 0x07); - - if ((current_component[4] & 0x80) && - global_color_map_colors > 0) { /* global map */ - global_color_map_.SetTablePositionAndSize(data_position, - global_color_map_colors); - GETN(kBytesPerColormapEntry * global_color_map_colors, - kGIFGlobalColormap); - break; - } - - GETN(1, kGIFImageStart); - break; - } - - case kGIFGlobalColormap: { - global_color_map_.SetDefined(); - GETN(1, kGIFImageStart); - break; - } - - case kGIFImageStart: { - const char current_component = - reader.GetOneByte(current_component_position); - - if (current_component == '!') { // extension. - GETN(2, kGIFExtension); - break; - } - - if (current_component == ',') { // image separator. - GETN(9, kGIFImageHeader); - break; - } - - // If we get anything other than ',' (image separator), '!' - // (extension), or ';' (trailer), there is extraneous data - // between blocks. The GIF87a spec tells us to keep reading - // until we find an image separator, but GIF89a says such - // a file is corrupt. We follow Mozilla's implementation and - // proceed as if the file were correctly terminated, so the - // GIF will display. - GETN(0, kGIFDone); - break; - } - - case kGIFExtension: { - const unsigned char* current_component = - reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( - current_component_position, 2, read_buffer)); - - size_t bytes_in_block = current_component[1]; - GIFState exception_state = kGIFSkipBlock; - - switch (*current_component) { - case 0xf9: - exception_state = kGIFControlExtension; - // The GIF spec mandates that the GIFControlExtension header block - // length is 4 bytes, and the parser for this block reads 4 bytes, - // so we must enforce that the buffer contains at least this many - // bytes. If the GIF specifies a different length, we allow that, so - // long as it's larger; the additional data will simply be ignored. - bytes_in_block = std::max(bytes_in_block, static_cast<size_t>(4)); - break; - - // The GIF spec also specifies the lengths of the following two - // extensions' headers (as 12 and 11 bytes, respectively). Because we - // ignore the plain text extension entirely and sanity-check the - // actual length of the application extension header before reading - // it, we allow GIFs to deviate from these values in either direction. - // This is important for real-world compatibility, as GIFs in the wild - // exist with application extension headers that are both shorter and - // longer than 11 bytes. - case 0x01: - // ignoring plain text extension - break; - - case 0xff: - exception_state = kGIFApplicationExtension; - break; - - case 0xfe: - exception_state = kGIFConsumeComment; - break; - } - - if (bytes_in_block) - GETN(bytes_in_block, exception_state); - else - GETN(1, kGIFImageStart); - break; - } - - case kGIFConsumeBlock: { - const unsigned char current_component = static_cast<unsigned char>( - reader.GetOneByte(current_component_position)); - if (!current_component) - GETN(1, kGIFImageStart); - else - GETN(current_component, kGIFSkipBlock); - break; - } - - case kGIFSkipBlock: { - GETN(1, kGIFConsumeBlock); - break; - } - - case kGIFControlExtension: { - const unsigned char* current_component = - reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( - current_component_position, 4, read_buffer)); - - AddFrameIfNecessary(); - GIFFrameContext* current_frame = frames_.back().get(); - if (*current_component & 0x1) - current_frame->SetTransparentPixel(current_component[3]); - - // We ignore the "user input" bit. - - // NOTE: This relies on the values in the FrameDisposalMethod enum - // matching those in the GIF spec! - int disposal_method = ((*current_component) >> 2) & 0x7; - if (disposal_method < 4) { - current_frame->SetDisposalMethod( - static_cast<ImageFrame::DisposalMethod>(disposal_method)); - } else if (disposal_method == 4) { - // Some specs say that disposal method 3 is "overwrite previous", - // others that setting the third bit of the field (i.e. method 4) is. - // We map both to the same value. - current_frame->SetDisposalMethod( - ImageFrame::kDisposeOverwritePrevious); - } - current_frame->SetDelayTime(GETINT16(current_component + 1) * 10); - GETN(1, kGIFConsumeBlock); - break; - } - - case kGIFCommentExtension: { - const unsigned char current_component = static_cast<unsigned char>( - reader.GetOneByte(current_component_position)); - if (current_component) - GETN(current_component, kGIFConsumeComment); - else - GETN(1, kGIFImageStart); - break; - } - - case kGIFConsumeComment: { - GETN(1, kGIFCommentExtension); - break; - } - - case kGIFApplicationExtension: { - // Check for netscape application extension. - if (bytes_to_consume_ == 11) { - const unsigned char* current_component = - reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( - current_component_position, 11, read_buffer)); - - if (!memcmp(current_component, "NETSCAPE2.0", 11) || - !memcmp(current_component, "ANIMEXTS1.0", 11)) - GETN(1, kGIFNetscapeExtensionBlock); - } - - if (state_ != kGIFNetscapeExtensionBlock) - GETN(1, kGIFConsumeBlock); - break; - } - - // Netscape-specific GIF extension: animation looping. - case kGIFNetscapeExtensionBlock: { - const int current_component = static_cast<unsigned char>( - reader.GetOneByte(current_component_position)); - // GIFConsumeNetscapeExtension always reads 3 bytes from the stream; we - // should at least wait for this amount. - if (current_component) - GETN(std::max(3, current_component), kGIFConsumeNetscapeExtension); - else - GETN(1, kGIFImageStart); - break; - } - - // Parse netscape-specific application extensions - case kGIFConsumeNetscapeExtension: { - const unsigned char* current_component = - reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( - current_component_position, 3, read_buffer)); - - int netscape_extension = current_component[0] & 7; - - // Loop entire animation specified # of times. Only read the loop count - // during the first iteration. - if (netscape_extension == 1) { - loop_count_ = GETINT16(current_component + 1); - - // Zero loop count is infinite animation loop request. - if (!loop_count_) - loop_count_ = kAnimationLoopInfinite; - - GETN(1, kGIFNetscapeExtensionBlock); - } else if (netscape_extension == 2) { - // Wait for specified # of bytes to enter buffer. - - // Don't do this, this extension doesn't exist (isn't used at all) - // and doesn't do anything, as our streaming/buffering takes care of - // it all. See http://semmix.pl/color/exgraf/eeg24.htm . - GETN(1, kGIFNetscapeExtensionBlock); - } else { - // 0,3-7 are yet to be defined netscape extension codes - return false; - } - break; - } - - case kGIFImageHeader: { - unsigned height, width, x_offset, y_offset; - const unsigned char* current_component = - reinterpret_cast<const unsigned char*>(reader.GetConsecutiveData( - current_component_position, 9, read_buffer)); - - /* Get image offsets, with respect to the screen origin */ - x_offset = GETINT16(current_component); - y_offset = GETINT16(current_component + 2); - - /* Get image width and height. */ - width = GETINT16(current_component + 4); - height = GETINT16(current_component + 6); - - // Some GIF files have frames that don't fit in the specified - // overall image size. For the first frame, we can simply enlarge - // the image size to allow the frame to be visible. We can't do - // this on subsequent frames because the rest of the decoding - // infrastructure assumes the image size won't change as we - // continue decoding, so any subsequent frames that are even - // larger will be cropped. - // Luckily, handling just the first frame is sufficient to deal - // with most cases, e.g. ones where the image size is erroneously - // set to zero, since usually the first frame completely fills - // the image. - if (CurrentFrameIsFirstFrame()) { - screen_height_ = std::max(screen_height_, y_offset + height); - screen_width_ = std::max(screen_width_, x_offset + width); - } - - // Inform the client of the final size. - if (!sent_size_to_client_ && client_ && - !client_->SetSize(screen_width_, screen_height_)) - return false; - sent_size_to_client_ = true; - - if (query == GIFImageDecoder::kGIFSizeQuery) { - // The decoder needs to stop. Hand back the number of bytes we - // consumed from the buffer minus 9 (the amount we consumed to read - // the header). - SetRemainingBytes(len + 9); - GETN(9, kGIFImageHeader); - return true; - } - - AddFrameIfNecessary(); - GIFFrameContext* current_frame = frames_.back().get(); - - current_frame->SetHeaderDefined(); - - // Work around more broken GIF files that have zero image width or - // height. - if (!height || !width) { - height = screen_height_; - width = screen_width_; - if (!height || !width) - return false; - } - current_frame->SetRect(x_offset, y_offset, width, height); - current_frame->SetInterlaced(current_component[8] & 0x40); - - // Overlaying interlaced, transparent GIFs over - // existing image data using the Haeberli display hack - // requires saving the underlying image in order to - // avoid jaggies at the transparency edges. We are - // unprepared to deal with that, so don't display such - // images progressively. Which means only the first - // frame can be progressively displayed. - // FIXME: It is possible that a non-transparent frame - // can be interlaced and progressively displayed. - current_frame->SetProgressiveDisplay(CurrentFrameIsFirstFrame()); - - const bool is_local_colormap_defined = current_component[8] & 0x80; - if (is_local_colormap_defined) { - // The three low-order bits of currentComponent[8] specify the bits - // per pixel. - const size_t num_colors = 2 << (current_component[8] & 0x7); - current_frame->LocalColorMap().SetTablePositionAndSize(data_position, - num_colors); - GETN(kBytesPerColormapEntry * num_colors, kGIFImageColormap); - break; - } - - GETN(1, kGIFLZWStart); - break; - } - - case kGIFImageColormap: { - DCHECK(!frames_.IsEmpty()); - frames_.back()->LocalColorMap().SetDefined(); - GETN(1, kGIFLZWStart); - break; - } - - case kGIFSubBlock: { - const size_t bytes_in_block = static_cast<unsigned char>( - reader.GetOneByte(current_component_position)); - if (bytes_in_block) { - GETN(bytes_in_block, GIFLZW); - } else { - // Finished parsing one frame; Process next frame. - DCHECK(!frames_.IsEmpty()); - // Note that some broken GIF files do not have enough LZW blocks to - // fully decode all rows; we treat this case as "frame complete". - frames_.back()->SetComplete(); - GETN(1, kGIFImageStart); - } - break; - } - - case kGIFDone: { - parse_completed_ = true; - return true; - } - - default: - // We shouldn't ever get here. - return false; - break; - } - } - - SetRemainingBytes(len); - return true; -} - -void GIFImageReader::SetRemainingBytes(size_t remaining_bytes) { - DCHECK_LE(remaining_bytes, data_->size()); - bytes_read_ = data_->size() - remaining_bytes; -} - -void GIFImageReader::AddFrameIfNecessary() { - if (frames_.IsEmpty() || frames_.back()->IsComplete()) - frames_.push_back(base::WrapUnique(new GIFFrameContext(frames_.size()))); -} - -// FIXME: Move this method to close to doLZW(). -bool GIFLZWContext::PrepareToDecode() { - DCHECK(frame_context_->IsDataSizeDefined()); - DCHECK(frame_context_->IsHeaderDefined()); - - // Since we use a codesize of 1 more than the datasize, we need to ensure - // that our datasize is strictly less than the kMaxDictionaryEntryBits. - if (frame_context_->DataSize() >= kMaxDictionaryEntryBits) - return false; - clear_code = 1 << frame_context_->DataSize(); - avail = clear_code + 2; - oldcode = -1; - codesize = frame_context_->DataSize() + 1; - codemask = (1 << codesize) - 1; - datum = bits = 0; - ipass = frame_context_->Interlaced() ? 1 : 0; - irow = 0; - - // We want to know the longest sequence encodable by a dictionary with - // kMaxDictionaryEntries entries. If we ignore the need to encode the base - // values themselves at the beginning of the dictionary, as well as the need - // for a clear code or a termination code, we could use every entry to - // encode a series of multiple values. If the input value stream looked - // like "AAAAA..." (a long string of just one value), the first dictionary - // entry would encode AA, the next AAA, the next AAAA, and so forth. Thus - // the longest sequence would be kMaxDictionaryEntries + 1 values. - // - // However, we have to account for reserved entries. The first |datasize| - // bits are reserved for the base values, and the next two entries are - // reserved for the clear code and termination code. In theory a GIF can - // set the datasize to 0, meaning we have just two reserved entries, making - // the longest sequence (kMaxDictionaryEntries + 1) - 2 values long. Since - // each value is a byte, this is also the number of bytes in the longest - // encodable sequence. - const size_t kMaxBytes = kMaxDictionaryEntries - 1; - - // Now allocate the output buffer. We decode directly into this buffer - // until we have at least one row worth of data, then call outputRow(). - // This means worst case we may have (row width - 1) bytes in the buffer - // and then decode a sequence |maxBytes| long to append. - row_buffer.resize(frame_context_->Width() - 1 + kMaxBytes); - row_iter = row_buffer.begin(); - rows_remaining = frame_context_->Height(); - - // Clearing the whole suffix table lets us be more tolerant of bad data. - for (int i = 0; i < clear_code; ++i) { - suffix[i] = i; - suffix_length[i] = 1; - } - return true; -} - -} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h b/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h deleted file mode 100644 index 8d56800b6f9..00000000000 --- a/chromium/third_party/blink/renderer/platform/image-decoders/gif/gif_image_reader.h +++ /dev/null @@ -1,371 +0,0 @@ -/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (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.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Mozilla Communicator client code. - * - * The Initial Developer of the Original Code is - * Netscape Communications Corporation. - * Portions created by the Initial Developer are Copyright (C) 1998 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_GIF_GIF_IMAGE_READER_H_ -#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_GIF_GIF_IMAGE_READER_H_ - -// Define ourselves as the clientPtr. Mozilla just hacked their C++ callback -// class into this old C decoder, so we will too. -#include <memory> -#include "third_party/blink/renderer/platform/image-decoders/gif/gif_image_decoder.h" -#include "third_party/blink/renderer/platform/wtf/allocator.h" -#include "third_party/blink/renderer/platform/wtf/noncopyable.h" -#include "third_party/blink/renderer/platform/wtf/vector.h" - -namespace blink { - -class FastSharedBufferReader; - -const int kCLoopCountNotSeen = -2; - -// List of possible parsing states. -enum GIFState { - kGIFType, - kGIFGlobalHeader, - kGIFGlobalColormap, - kGIFImageStart, - kGIFImageHeader, - kGIFImageColormap, - kGIFImageBody, - kGIFLZWStart, - GIFLZW, - kGIFSubBlock, - kGIFExtension, - kGIFControlExtension, - kGIFConsumeBlock, - kGIFSkipBlock, - kGIFDone, - kGIFCommentExtension, - kGIFApplicationExtension, - kGIFNetscapeExtensionBlock, - kGIFConsumeNetscapeExtension, - kGIFConsumeComment -}; - -struct GIFFrameContext; - -// LZW decoder state machine. -class GIFLZWContext final { - USING_FAST_MALLOC(GIFLZWContext); - WTF_MAKE_NONCOPYABLE(GIFLZWContext); - - public: - GIFLZWContext(blink::GIFImageDecoder* client, - const GIFFrameContext* frame_context) - : codesize(0), - codemask(0), - clear_code(0), - avail(0), - oldcode(0), - firstchar(0), - bits(0), - datum(0), - ipass(0), - irow(0), - rows_remaining(0), - row_iter(nullptr), - client_(client), - frame_context_(frame_context) {} - - bool PrepareToDecode(); - bool OutputRow(GIFRow::const_iterator row_begin); - bool DoLZW(const unsigned char* block, size_t bytes_in_block); - bool HasRemainingRows() { return rows_remaining; } - - private: - enum { - kMaxDictionaryEntryBits = 12, - // 2^kMaxDictionaryEntryBits - kMaxDictionaryEntries = 4096, - }; - - // LZW decoding states and output states. - int codesize; - int codemask; - int clear_code; // Codeword used to trigger dictionary reset. - int avail; // Index of next available slot in dictionary. - int oldcode; - unsigned char firstchar; - int bits; // Number of unread bits in "datum". - int datum; // 32-bit input buffer. - int ipass; // Interlace pass; Ranges 1-4 if interlaced. - size_t irow; // Current output row, starting at zero. - size_t rows_remaining; // Rows remaining to be output. - - unsigned short prefix[kMaxDictionaryEntries]; - unsigned char suffix[kMaxDictionaryEntries]; - unsigned short suffix_length[kMaxDictionaryEntries]; - GIFRow row_buffer; // Single scanline temporary buffer. - GIFRow::iterator row_iter; - - // Initialized during construction and read-only. - blink::GIFImageDecoder* client_; - const GIFFrameContext* frame_context_; -}; - -// Data structure for one LZW block. -struct GIFLZWBlock { - DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); - - public: - GIFLZWBlock(size_t position, size_t size) - : block_position(position), block_size(size) {} - - size_t block_position; - size_t block_size; -}; - -class GIFColorMap final { - DISALLOW_NEW(); - - public: - typedef Vector<blink::ImageFrame::PixelData> Table; - - GIFColorMap() : is_defined_(false), position_(0), colors_(0) {} - - // Set position and number of colors for the RGB table in the data stream. - void SetTablePositionAndSize(size_t position, size_t colors) { - position_ = position; - colors_ = colors; - } - void SetDefined() { is_defined_ = true; } - bool IsDefined() const { return is_defined_; } - - // Build RGBA table using the data stream. - void BuildTable(blink::FastSharedBufferReader*); - const Table& GetTable() const { return table_; } - - private: - bool is_defined_; - size_t position_; - size_t colors_; - Table table_; -}; - -// LocalFrame output state machine. -struct GIFFrameContext { - USING_FAST_MALLOC(GIFFrameContext); - WTF_MAKE_NONCOPYABLE(GIFFrameContext); - - public: - GIFFrameContext(int id) - : frame_id_(id), - x_offset_(0), - y_offset_(0), - width_(0), - height_(0), - transparent_pixel_(kNotFound), - disposal_method_(blink::ImageFrame::kDisposeNotSpecified), - data_size_(0), - progressive_display_(false), - interlaced_(false), - delay_time_(0), - current_lzw_block_(0), - is_complete_(false), - is_header_defined_(false), - is_data_size_defined_(false) {} - - ~GIFFrameContext() = default; - - void AddLzwBlock(size_t position, size_t size) { - lzw_blocks_.push_back(GIFLZWBlock(position, size)); - } - - bool Decode(blink::FastSharedBufferReader*, - blink::GIFImageDecoder* client, - bool* frame_decoded); - - int FrameId() const { return frame_id_; } - void SetRect(unsigned x, unsigned y, unsigned width, unsigned height) { - x_offset_ = x; - y_offset_ = y; - width_ = width; - height_ = height; - } - blink::IntRect FrameRect() const { - return blink::IntRect(x_offset_, y_offset_, width_, height_); - } - unsigned XOffset() const { return x_offset_; } - unsigned YOffset() const { return y_offset_; } - unsigned Width() const { return width_; } - unsigned Height() const { return height_; } - size_t TransparentPixel() const { return transparent_pixel_; } - void SetTransparentPixel(size_t pixel) { transparent_pixel_ = pixel; } - blink::ImageFrame::DisposalMethod GetDisposalMethod() const { - return disposal_method_; - } - void SetDisposalMethod(blink::ImageFrame::DisposalMethod disposal_method) { - disposal_method_ = disposal_method; - } - unsigned DelayTime() const { return delay_time_; } - void SetDelayTime(unsigned delay) { delay_time_ = delay; } - bool IsComplete() const { return is_complete_; } - void SetComplete() { is_complete_ = true; } - bool IsHeaderDefined() const { return is_header_defined_; } - void SetHeaderDefined() { is_header_defined_ = true; } - bool IsDataSizeDefined() const { return is_data_size_defined_; } - int DataSize() const { return data_size_; } - void SetDataSize(int size) { - data_size_ = size; - is_data_size_defined_ = true; - } - bool ProgressiveDisplay() const { return progressive_display_; } - void SetProgressiveDisplay(bool progressive_display) { - progressive_display_ = progressive_display; - } - bool Interlaced() const { return interlaced_; } - void SetInterlaced(bool interlaced) { interlaced_ = interlaced; } - - void ClearDecodeState() { lzw_context_.reset(); } - const GIFColorMap& LocalColorMap() const { return local_color_map_; } - GIFColorMap& LocalColorMap() { return local_color_map_; } - - private: - int frame_id_; - unsigned x_offset_; - unsigned y_offset_; // With respect to "screen" origin. - unsigned width_; - unsigned height_; - size_t transparent_pixel_; // Index of transparent pixel. Value is kNotFound - // if there is no transparent pixel. - blink::ImageFrame::DisposalMethod - disposal_method_; // Restore to background, leave in place, etc. - int data_size_; - - bool progressive_display_; // If true, do Haeberli interlace hack. - bool interlaced_; // True, if scanlines arrive interlaced order. - - unsigned delay_time_; // Display time, in milliseconds, for this image in a - // multi-image GIF. - - std::unique_ptr<GIFLZWContext> lzw_context_; - Vector<GIFLZWBlock> lzw_blocks_; // LZW blocks for this frame. - GIFColorMap local_color_map_; - - size_t current_lzw_block_; - bool is_complete_; - bool is_header_defined_; - bool is_data_size_defined_; -}; - -class PLATFORM_EXPORT GIFImageReader final { - USING_FAST_MALLOC(GIFImageReader); - WTF_MAKE_NONCOPYABLE(GIFImageReader); - - public: - GIFImageReader(blink::GIFImageDecoder* client = nullptr) - : client_(client), - state_(kGIFType), - // Number of bytes for GIF type, either "GIF87a" or "GIF89a". - bytes_to_consume_(6), - bytes_read_(0), - version_(0), - screen_width_(0), - screen_height_(0), - sent_size_to_client_(false), - loop_count_(kCLoopCountNotSeen), - parse_completed_(false) {} - - ~GIFImageReader() = default; - - void SetData(scoped_refptr<blink::SegmentReader> data) { - data_ = std::move(data); - } - bool Parse(blink::GIFImageDecoder::GIFParseQuery); - bool Decode(size_t frame_index); - - size_t ImagesCount() const { - if (frames_.IsEmpty()) - return 0; - - // This avoids counting an empty frame when the file is truncated right - // after GIFControlExtension but before GIFImageHeader. - // FIXME: This extra complexity is not necessary and we should just report - // m_frames.size(). - return frames_.back()->IsHeaderDefined() ? frames_.size() - : frames_.size() - 1; - } - int LoopCount() const { return loop_count_; } - - const GIFColorMap& GlobalColorMap() const { return global_color_map_; } - - const GIFFrameContext* FrameContext(size_t index) const { - return index < frames_.size() ? frames_[index].get() : nullptr; - } - - bool ParseCompleted() const { return parse_completed_; } - - void ClearDecodeState(size_t index) { frames_[index]->ClearDecodeState(); } - - private: - bool ParseData(size_t data_position, - size_t len, - blink::GIFImageDecoder::GIFParseQuery); - void SetRemainingBytes(size_t); - - void AddFrameIfNecessary(); - bool CurrentFrameIsFirstFrame() const { - return frames_.IsEmpty() || - (frames_.size() == 1u && !frames_[0]->IsComplete()); - } - - blink::GIFImageDecoder* client_; - - // Parsing state machine. - GIFState state_; // Current decoder master state. - size_t bytes_to_consume_; // Number of bytes to consume for next stage of - // parsing. - size_t bytes_read_; // Number of bytes processed. - - // Global (multi-image) state. - int version_; // Either 89 for GIF89 or 87 for GIF87. - unsigned screen_width_; // Logical screen width & height. - unsigned screen_height_; - bool sent_size_to_client_; - GIFColorMap global_color_map_; - int loop_count_; // Netscape specific extension block to control the number - // of animation loops a GIF renders. - - Vector<std::unique_ptr<GIFFrameContext>> frames_; - - scoped_refptr<blink::SegmentReader> data_; - bool parse_completed_; -}; - -} // namespace blink - -#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h b/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h index 5524ec50702..cdd93707c9a 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h +++ b/chromium/third_party/blink/renderer/platform/image-decoders/ico/ico_image_decoder.h @@ -73,7 +73,7 @@ class PLATFORM_EXPORT ICOImageDecoder final : public ImageDecoder { }; struct IconDirectoryEntry { - DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + DISALLOW_NEW(); IntSize size_; uint16_t bit_count_; IntPoint hot_spot_; diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.h b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.h index 1242a2ed840..c620481aa73 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.h +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_decoder.h @@ -41,9 +41,10 @@ #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" #include "third_party/blink/renderer/platform/wtf/time.h" #include "third_party/blink/renderer/platform/wtf/vector.h" -#include "third_party/skia/include/core/SkColorSpaceXform.h" #include "third_party/skia/third_party/skcms/skcms.h" +class SkColorSpace; + namespace blink { #if SK_B32_SHIFT @@ -118,7 +119,7 @@ class PLATFORM_EXPORT ImageDecoder { enum AlphaOption { kAlphaPremultiplied, kAlphaNotPremultiplied }; enum HighBitDepthDecodingOption { - // Decode everything to 8-8-8-8 pixel format (kN32 channel order). + // Decode everything to uint8 pixel format (kN32 channel order). kDefaultBitDepth, // Decode high bit depth images to half float pixel format. kHighBitDepthToHalfFloat @@ -302,15 +303,6 @@ class PLATFORM_EXPORT ImageDecoder { // Callers may pass WTF::kNotFound to clear all frames. // Note: If |frame_buffer_cache_| contains only one frame, it won't be // cleared. Returns the number of bytes of frame data actually cleared. - // - // This is a virtual method because MockImageDecoder needs to override it in - // order to run the test ImageFrameGeneratorTest::ClearMultiFrameDecode. - // - // @TODO Let MockImageDecoder override ImageFrame::ClearFrameBuffer instead, - // so this method can be made non-virtual. It is used in the test - // ImageFrameGeneratorTest::ClearMultiFrameDecode. The test needs to - // be modified since two frames may be kept in cache, instead of - // always just one, with this ClearCacheExceptFrame implementation. virtual size_t ClearCacheExceptFrame(size_t); // If the image has a cursor hot-spot, stores it in the argument @@ -453,7 +445,9 @@ class PLATFORM_EXPORT ImageDecoder { // |index| is smaller than |frame_buffer_cache_|.size(). virtual bool FrameStatusSufficientForSuccessors(size_t index) { DCHECK(index < frame_buffer_cache_.size()); - return frame_buffer_cache_[index].GetStatus() != ImageFrame::kFrameEmpty; + ImageFrame::Status frame_status = frame_buffer_cache_[index].GetStatus(); + return frame_status == ImageFrame::kFramePartial || + frame_status == ImageFrame::kFrameComplete; } private: diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.cc b/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.cc index 02ed602282d..99b5cd14ee3 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.cc +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.cc @@ -29,7 +29,7 @@ #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h" #include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" #include "third_party/skia/include/core/SkCanvas.h" -#include "third_party/skia/include/core/SkColorSpaceXform.h" +#include "third_party/skia/include/core/SkColorSpace.h" #include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkSurface.h" @@ -89,9 +89,16 @@ bool ImageFrame::CopyBitmapData(const ImageFrame& other) { pixel_format_ = other.pixel_format_; bitmap_.reset(); SkImageInfo info = other.bitmap_.info(); - return bitmap_.tryAllocPixels(info) && - other.bitmap_.readPixels(info, bitmap_.getPixels(), bitmap_.rowBytes(), - 0, 0); + if (!bitmap_.tryAllocPixels(info)) { + return false; + } + + if (!other.bitmap_.readPixels(info, bitmap_.getPixels(), bitmap_.rowBytes(), + 0, 0)) + return false; + + status_ = kFrameInitialized; + return true; } bool ImageFrame::TakeBitmapDataIfWritable(ImageFrame* other) { @@ -106,6 +113,7 @@ bool ImageFrame::TakeBitmapDataIfWritable(ImageFrame* other) { bitmap_.reset(); bitmap_.swap(other->bitmap_); other->status_ = kFrameEmpty; + status_ = kFrameInitialized; return true; } @@ -122,7 +130,11 @@ bool ImageFrame::AllocatePixelData(int new_width, if (pixel_format_ == kRGBA_F16) info = info.makeColorType(kRGBA_F16_SkColorType); bitmap_.setInfo(info); - return bitmap_.tryAllocPixels(allocator_); + bool allocated = bitmap_.tryAllocPixels(allocator_); + if (allocated) + status_ = kFrameInitialized; + + return allocated; } sk_sp<SkImage> ImageFrame::FinalizePixelsAndGetImage() { diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.h b/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.h index 9562391d7b2..6ad4ed48b6f 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.h +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_frame.h @@ -43,11 +43,11 @@ namespace blink { // ImageFrame represents the decoded image data. This buffer is what all // decoders write a single frame into. class PLATFORM_EXPORT ImageFrame final { - DISALLOW_NEW_EXCEPT_PLACEMENT_NEW(); + DISALLOW_NEW(); public: enum PixelFormat { kN32, kRGBA_F16 }; - enum Status { kFrameEmpty, kFramePartial, kFrameComplete }; + enum Status { kFrameEmpty, kFrameInitialized, kFramePartial, kFrameComplete }; enum DisposalMethod { // If you change the numeric values of these, make sure you audit // all users, as some users may cast raw values to/from these diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/image_frame_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/image_frame_test.cc index 908a772ff33..2b63ae6efa4 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/image_frame_test.cc +++ b/chromium/third_party/blink/renderer/platform/image-decoders/image_frame_test.cc @@ -5,7 +5,7 @@ #include "third_party/blink/renderer/platform/image-decoders/image_frame.h" #include "testing/gtest/include/gtest/gtest.h" -#include "third_party/skia/include/core/SkColorSpaceXform.h" +#include "third_party/skia/third_party/skcms/skcms.h" namespace blink { namespace { @@ -26,20 +26,16 @@ class ImageFrameTest : public testing::Test { src_8888 = SkPackARGB32(src_8888_a, src_8888_r, src_8888_g, src_8888_b); dst_8888 = SkPackARGB32(0xA0, 0x60, 0x70, 0x80); - typedef SkColorSpaceXform::ColorFormat ColorFormat; - color_format_8888 = ColorFormat::kBGRA_8888_ColorFormat; + pixel_format_n32 = skcms_PixelFormat_RGBA_8888; if (kN32_SkColorType == kRGBA_8888_SkColorType) - color_format_8888 = ColorFormat::kRGBA_8888_ColorFormat; - color_format_f16 = ColorFormat::kRGBA_F16_ColorFormat; - color_format_f32 = ColorFormat::kRGBA_F32_ColorFormat; - - sk_sp<SkColorSpace> srgb_linear = SkColorSpace::MakeSRGBLinear(); - SkColorSpaceXform::Apply(srgb_linear.get(), color_format_f16, &src_f16, - srgb_linear.get(), color_format_8888, &src_8888, 1, - SkColorSpaceXform::AlphaOp::kPreserve_AlphaOp); - SkColorSpaceXform::Apply(srgb_linear.get(), color_format_f16, &dst_f16, - srgb_linear.get(), color_format_8888, &dst_8888, 1, - SkColorSpaceXform::AlphaOp::kPreserve_AlphaOp); + pixel_format_n32 = skcms_PixelFormat_BGRA_8888; + + skcms_Transform(&src_8888, pixel_format_n32, skcms_AlphaFormat_Unpremul, + nullptr, &src_f16, skcms_PixelFormat_RGBA_hhhh, + skcms_AlphaFormat_Unpremul, nullptr, 1); + skcms_Transform(&dst_8888, pixel_format_n32, skcms_AlphaFormat_Unpremul, + nullptr, &dst_f16, skcms_PixelFormat_RGBA_hhhh, + skcms_AlphaFormat_Unpremul, nullptr, 1); } protected: @@ -47,21 +43,19 @@ class ImageFrameTest : public testing::Test { unsigned src_8888_a, src_8888_r, src_8888_g, src_8888_b; ImageFrame::PixelData src_8888, dst_8888; ImageFrame::PixelDataF16 src_f16, dst_f16; - SkColorSpaceXform::ColorFormat color_format_8888, color_format_f16, - color_format_f32; + skcms_PixelFormat pixel_format_n32; void ConvertN32ToF32(float* dst, ImageFrame::PixelData src) { - sk_sp<SkColorSpace> srgb_linear = SkColorSpace::MakeSRGBLinear(); - SkColorSpaceXform::Apply(srgb_linear.get(), color_format_f32, dst, - srgb_linear.get(), color_format_8888, &src, 1, - SkColorSpaceXform::AlphaOp::kPreserve_AlphaOp); + skcms_Transform(&src, pixel_format_n32, skcms_AlphaFormat_Unpremul, nullptr, + dst, skcms_PixelFormat_RGBA_ffff, + skcms_AlphaFormat_Unpremul, nullptr, 1); } void ConvertF16ToF32(float* dst, ImageFrame::PixelDataF16 src) { - sk_sp<SkColorSpace> srgb_linear = SkColorSpace::MakeSRGBLinear(); - SkColorSpaceXform::Apply(srgb_linear.get(), color_format_f32, dst, - srgb_linear.get(), color_format_f16, &src, 1, - SkColorSpaceXform::AlphaOp::kPreserve_AlphaOp); + skcms_Transform(&src, skcms_PixelFormat_RGBA_hhhh, + skcms_AlphaFormat_Unpremul, nullptr, dst, + skcms_PixelFormat_RGBA_ffff, skcms_AlphaFormat_Unpremul, + nullptr, 1); } }; diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc b/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc index 5792b23b144..f08fbb3d5ac 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc +++ b/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.cc @@ -38,10 +38,8 @@ #include "third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder.h" #include <memory> -#include "base/numerics/safe_conversions.h" #include "build/build_config.h" -#include "third_party/blink/renderer/platform/geometry/int_size.h" -#include "third_party/blink/renderer/platform/histogram.h" +#include "third_party/blink/renderer/platform/graphics/bitmap_image_metrics.h" #include "third_party/blink/renderer/platform/instrumentation/platform_instrumentation.h" extern "C" { @@ -80,24 +78,64 @@ namespace { const int exifMarker = JPEG_APP0 + 1; // JPEG only supports a denominator of 8. -const unsigned g_scale_denomiator = 8; - -// Configuration for the JPEG image area histogram. See RecordJpegImageArea(). -const char* kImageAreaHistogramName = "Blink.ImageDecoders.Jpeg.Area"; -constexpr base::HistogramBase::Sample kImageAreaHistogramMin = 1; -constexpr base::HistogramBase::Sample kImageAreaHistogramMax = 8192 * 8192; -constexpr int32_t kImageAreaHistogramBucketCount = 100; - -// Records the area (total number of pixels) of a JPEG image as a UMA. -void RecordJpegImageArea(const blink::IntSize& size) { - DEFINE_THREAD_SAFE_STATIC_LOCAL( - blink::CustomCountHistogram, image_area_histogram, - (kImageAreaHistogramName, kImageAreaHistogramMin, kImageAreaHistogramMax, - kImageAreaHistogramBucketCount)); - // A base::HistogramBase::Sample may not fit |size.Area()|. Hence the use of - // saturated_cast. - image_area_histogram.Count( - base::saturated_cast<base::HistogramBase::Sample>(size.Area())); +const unsigned g_scale_denominator = 8; + +// Extracts the JPEG color space of an image for UMA purposes given |info| which +// is assumed to have gone through a jpeg_read_header(). When the color space is +// YCbCr, we also extract the chroma subsampling. The caveat is that the +// extracted color space is really libjpeg_turbo's guess. According to +// libjpeg.txt, "[t]he JPEG color space, unfortunately, is something of a guess +// since the JPEG standard proper does not provide a way to record it. In +// practice most files adhere to the JFIF or Adobe conventions, and the decoder +// will recognize these correctly." +blink::BitmapImageMetrics::JpegColorSpace ExtractUMAJpegColorSpace( + const jpeg_decompress_struct& info) { + switch (info.jpeg_color_space) { + case JCS_GRAYSCALE: + return blink::BitmapImageMetrics::JpegColorSpace::kGrayscale; + case JCS_RGB: + return blink::BitmapImageMetrics::JpegColorSpace::kRGB; + case JCS_CMYK: + return blink::BitmapImageMetrics::JpegColorSpace::kCMYK; + case JCS_YCCK: + return blink::BitmapImageMetrics::JpegColorSpace::kYCCK; + case JCS_YCbCr: + // The following logic is mostly reused from YuvSubsampling(). However, + // here we use |info.comp_info| instead of |info.cur_comp_info| to read + // the components from the SOF instead of the first scan. We also don't + // care about |info.scale_denom|. + // TODO: can we use this same logic in YuvSubsampling()? + if (info.num_components == 3 && info.comp_info && + info.comp_info[1].h_samp_factor == 1 && + info.comp_info[1].v_samp_factor == 1 && + info.comp_info[2].h_samp_factor == 1 && + info.comp_info[2].v_samp_factor == 1) { + const int h = info.comp_info[0].h_samp_factor; + const int v = info.comp_info[0].v_samp_factor; + if (v == 1) { + switch (h) { + case 1: + return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr444; + case 2: + return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr422; + case 4: + return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr411; + } + } else if (v == 2) { + switch (h) { + case 1: + return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr440; + case 2: + return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr420; + case 4: + return blink::BitmapImageMetrics::JpegColorSpace::kYCbCr410; + } + } + } + return blink::BitmapImageMetrics::JpegColorSpace::kYCbCrOther; + default: + return blink::BitmapImageMetrics::JpegColorSpace::kUnknown; + } } } // namespace @@ -413,6 +451,21 @@ class JPEGImageReader final { ClearBuffer(); } + bool ShouldDecodeToOriginalSize() const { + // We should decode only to original size if either dimension cannot fit a + // whole number of MCUs. + const int max_h_samp_factor = info_.max_h_samp_factor; + const int max_v_samp_factor = info_.max_v_samp_factor; + DCHECK_GE(max_h_samp_factor, 1); + DCHECK_GE(max_v_samp_factor, 1); + DCHECK_LE(max_h_samp_factor, 4); + DCHECK_LE(max_v_samp_factor, 4); + const int mcu_width = info_.max_h_samp_factor * DCTSIZE; + const int mcu_height = info_.max_v_samp_factor * DCTSIZE; + return info_.image_width % mcu_width != 0 || + info_.image_height % mcu_height != 0; + } + // Decode the JPEG data. If |only_size| is specified, then only the size // information will be decoded. bool Decode(bool only_size) { @@ -458,16 +511,37 @@ class JPEGImageReader final { // Calculate and set decoded size. int max_numerator = decoder_->DesiredScaleNumerator(); - info_.scale_denom = g_scale_denomiator; + info_.scale_denom = g_scale_denominator; if (decoder_->ShouldGenerateAllSizes()) { + // Some images should not be scaled down by libjpeg_turbo because + // doing so may cause artifacts. Specifically, if the image contains a + // non-whole number of MCUs in either dimension, it's possible that + // the encoder used bogus data to create the last row or column of + // MCUs. This data may manifest when downscaling using libjpeg_turbo. + // See https://crbug.com/890745 and + // https://github.com/libjpeg-turbo/libjpeg-turbo/issues/297. Hence, + // we'll only allow downscaling an image if both dimensions fit a + // whole number of MCUs or if decoding to the original size would + // cause us to exceed memory limits. The latter case is detected by + // checking the |max_numerator| returned by DesiredScaleNumerator(): + // this method will return either |g_scale_denominator| if decoding to + // the original size won't exceed the memory limit (see + // |max_decoded_bytes_| in ImageDecoder) or something less than + // |g_scale_denominator| otherwise to ensure the image is downscaled. std::vector<SkISize> sizes; - sizes.reserve(max_numerator); - for (int numerator = 1; numerator <= max_numerator; ++numerator) { - info_.scale_num = numerator; - jpeg_calc_output_dimensions(&info_); + if (max_numerator == g_scale_denominator && + ShouldDecodeToOriginalSize()) { sizes.push_back( - SkISize::Make(info_.output_width, info_.output_height)); + SkISize::Make(info_.image_width, info_.image_height)); + } else { + sizes.reserve(max_numerator); + for (int numerator = 1; numerator <= max_numerator; ++numerator) { + info_.scale_num = numerator; + jpeg_calc_output_dimensions(&info_); + sizes.push_back( + SkISize::Make(info_.output_width, info_.output_height)); + } } decoder_->SetSupportedDecodeSizes(std::move(sizes)); } @@ -652,7 +726,9 @@ class JPEGImageReader final { case JPEG_DONE: // Finish decompression. - RecordJpegImageArea(decoder_->Size()); + BitmapImageMetrics::CountJpegArea(decoder_->Size()); + BitmapImageMetrics::CountJpegColorSpace( + ExtractUMAJpegColorSpace(info_)); return jpeg_finish_decompress(&info_); } @@ -828,13 +904,13 @@ unsigned JPEGImageDecoder::DesiredScaleNumerator() const { size_t original_bytes = Size().Width() * Size().Height() * 4; if (original_bytes <= max_decoded_bytes_) - return g_scale_denomiator; + return g_scale_denominator; // Downsample according to the maximum decoded size. unsigned scale_numerator = static_cast<unsigned>(floor(sqrt( // MSVC needs explicit parameter type for sqrt(). - static_cast<float>(max_decoded_bytes_ * g_scale_denomiator * - g_scale_denomiator / original_bytes)))); + static_cast<float>(max_decoded_bytes_ * g_scale_denominator * + g_scale_denominator / original_bytes)))); return scale_numerator; } diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc index bbc8ebf1cfa..734ee908140 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc +++ b/chromium/third_party/blink/renderer/platform/image-decoders/jpeg/jpeg_image_decoder_test.cc @@ -38,9 +38,11 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/blink/public/platform/web_data.h" #include "third_party/blink/public/platform/web_size.h" +#include "third_party/blink/renderer/platform/graphics/bitmap_image_metrics.h" #include "third_party/blink/renderer/platform/image-decoders/image_animation.h" #include "third_party/blink/renderer/platform/image-decoders/image_decoder_test_helpers.h" #include "third_party/blink/renderer/platform/shared_buffer.h" +#include "third_party/blink/renderer/platform/testing/histogram_tester.h" #include "third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer.h" namespace blink { @@ -336,7 +338,10 @@ TEST(JPEGImageDecoderTest, SupportedSizesSquare) { } TEST(JPEGImageDecoderTest, SupportedSizesRectangle) { - const char* jpeg_file = "/images/resources/icc-v2-gbr.jpg"; // 275x207 + // This 272x200 image uses 4:2:2 sampling format. The MCU is therefore 16x8. + // The width is a multiple of 16 and the height is a multiple of 8, so it's + // okay for the decoder to downscale it. + const char* jpeg_file = "/images/resources/icc-v2-gbr-422-whole-mcus.jpg"; scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file); ASSERT_TRUE(data); @@ -347,9 +352,41 @@ TEST(JPEGImageDecoderTest, SupportedSizesRectangle) { // This will decode the size and needs to be called to avoid DCHECKs ASSERT_TRUE(decoder->IsSizeAvailable()); std::vector<SkISize> expected_sizes = { + SkISize::Make(34, 25), SkISize::Make(68, 50), SkISize::Make(102, 75), + SkISize::Make(136, 100), SkISize::Make(170, 125), SkISize::Make(204, 150), + SkISize::Make(238, 175), SkISize::Make(272, 200)}; + + auto sizes = decoder->GetSupportedDecodeSizes(); + ASSERT_EQ(expected_sizes.size(), sizes.size()); + for (size_t i = 0; i < sizes.size(); ++i) { + EXPECT_TRUE(expected_sizes[i] == sizes[i]) + << "Expected " << expected_sizes[i].width() << "x" + << expected_sizes[i].height() << ". Got " << sizes[i].width() << "x" + << sizes[i].height(); + } +} + +TEST(JPEGImageDecoderTest, + SupportedSizesRectangleNotMultipleOfMCUIfMemoryBound) { + // This 275x207 image uses 4:2:0 sampling format. The MCU is therefore 16x16. + // Neither the width nor the height is a multiple of the MCU, so downscaling + // should not be supported. However, we limit the memory so that the decoder + // is forced to support downscaling. + const char* jpeg_file = "/images/resources/icc-v2-gbr.jpg"; + + scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file); + ASSERT_TRUE(data); + + // Make the memory limit one fewer byte than what is needed in order to force + // downscaling. + std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(275 * 207 * 4 - 1); + decoder->SetData(data.get(), true); + // This will decode the size and needs to be called to avoid DCHECKs + ASSERT_TRUE(decoder->IsSizeAvailable()); + std::vector<SkISize> expected_sizes = { SkISize::Make(35, 26), SkISize::Make(69, 52), SkISize::Make(104, 78), SkISize::Make(138, 104), SkISize::Make(172, 130), SkISize::Make(207, 156), - SkISize::Make(241, 182), SkISize::Make(275, 207)}; + SkISize::Make(241, 182)}; auto sizes = decoder->GetSupportedDecodeSizes(); ASSERT_EQ(expected_sizes.size(), sizes.size()); @@ -361,6 +398,38 @@ TEST(JPEGImageDecoderTest, SupportedSizesRectangle) { } } +TEST(JPEGImageDecoderTest, SupportedSizesRectangleNotMultipleOfMCU) { + struct { + const char* jpeg_file; + SkISize expected_size; + } recs[] = { + {// This 264x192 image uses 4:2:0 sampling format. The MCU is therefore + // 16x16. The height is a multiple of 16, but the width is not a + // multiple of 16, so it's not okay for the decoder to downscale it. + "/images/resources/icc-v2-gbr-420-width-not-whole-mcu.jpg", + SkISize::Make(264, 192)}, + {// This 272x200 image uses 4:2:0 sampling format. The MCU is therefore + // 16x16. The width is a multiple of 16, but the width is not a multiple + // of 16, so it's not okay for the decoder to downscale it. + "/images/resources/icc-v2-gbr-420-height-not-whole-mcu.jpg", + SkISize::Make(272, 200)}}; + for (const auto& rec : recs) { + scoped_refptr<SharedBuffer> data = ReadFile(rec.jpeg_file); + ASSERT_TRUE(data); + std::unique_ptr<ImageDecoder> decoder = + CreateJPEGDecoder(std::numeric_limits<int>::max()); + decoder->SetData(data.get(), true); + // This will decode the size and needs to be called to avoid DCHECKs + ASSERT_TRUE(decoder->IsSizeAvailable()); + auto sizes = decoder->GetSupportedDecodeSizes(); + ASSERT_EQ(1u, sizes.size()); + EXPECT_EQ(rec.expected_size, sizes[0]) + << "Expected " << rec.expected_size.width() << "x" + << rec.expected_size.height() << ". Got " << sizes[0].width() << "x" + << sizes[0].height(); + } +} + TEST(JPEGImageDecoderTest, SupportedSizesTruncatedIfMemoryBound) { const char* jpeg_file = "/images/resources/lenna.jpg"; // 256x256 scoped_refptr<SharedBuffer> data = ReadFile(jpeg_file); @@ -384,4 +453,90 @@ TEST(JPEGImageDecoderTest, SupportedSizesTruncatedIfMemoryBound) { } } +struct ColorSpaceUMATestParam { + std::string file; + bool expected_success; + BitmapImageMetrics::JpegColorSpace expected_color_space; +}; + +class ColorSpaceUMATest + : public ::testing::TestWithParam<ColorSpaceUMATestParam> {}; + +// Tests that the JPEG color space/subsampling is recorded correctly as a UMA +// for a variety of images. When the decode fails, no UMA should be recorded. +TEST_P(ColorSpaceUMATest, CorrectColorSpaceRecorded) { + HistogramTester histogram_tester; + scoped_refptr<SharedBuffer> data = + ReadFile(("/images/resources/" + GetParam().file).c_str()); + ASSERT_TRUE(data); + + std::unique_ptr<ImageDecoder> decoder = CreateJPEGDecoder(); + decoder->SetData(data.get(), true); + + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + ASSERT_TRUE(frame); + + if (GetParam().expected_success) { + ASSERT_FALSE(decoder->Failed()); + histogram_tester.ExpectUniqueSample("Blink.ImageDecoders.Jpeg.ColorSpace", + GetParam().expected_color_space, 1); + } else { + ASSERT_TRUE(decoder->Failed()); + histogram_tester.ExpectTotalCount("Blink.ImageDecoders.Jpeg.ColorSpace", 0); + } +} + +const ColorSpaceUMATest::ParamType kColorSpaceUMATestParams[] = { + {"cs-uma-grayscale.jpg", true, + BitmapImageMetrics::JpegColorSpace::kGrayscale}, + {"cs-uma-rgb.jpg", true, BitmapImageMetrics::JpegColorSpace::kRGB}, + // Each component is in a separate plane. Should not make a difference. + {"cs-uma-rgb-non-interleaved.jpg", true, + BitmapImageMetrics::JpegColorSpace::kRGB}, + {"cs-uma-cmyk.jpg", true, BitmapImageMetrics::JpegColorSpace::kCMYK}, + // 4 components/no markers, so we expect libjpeg_turbo to guess CMYK. + {"cs-uma-cmyk-no-jfif-or-adobe-markers.jpg", true, + BitmapImageMetrics::JpegColorSpace::kCMYK}, + // 4 components are not legal in JFIF, but we expect libjpeg_turbo to guess + // CMYK. + {"cs-uma-cmyk-jfif-marker.jpg", true, + BitmapImageMetrics::JpegColorSpace::kCMYK}, + {"cs-uma-ycck.jpg", true, BitmapImageMetrics::JpegColorSpace::kYCCK}, + // Contains CMYK data but uses a bad Adobe color transform, so libjpeg_turbo + // will guess YCCK. + {"cs-uma-cmyk-unknown-transform.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCCK}, + {"cs-uma-ycbcr-410.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCbCr410}, + {"cs-uma-ycbcr-411.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCbCr411}, + {"cs-uma-ycbcr-420.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCbCr420}, + // Each component is in a separate plane. Should not make a difference. + {"cs-uma-ycbcr-420-non-interleaved.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCbCr420}, + // 3 components/both JFIF and Adobe markers, so we expect libjpeg_turbo to + // guess YCbCr. + {"cs-uma-ycbcr-420-both-jfif-adobe.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCbCr420}, + {"cs-uma-ycbcr-422.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCbCr422}, + {"cs-uma-ycbcr-440.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCbCr440}, + {"cs-uma-ycbcr-444.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCbCr444}, + // Contains RGB data but uses a bad Adobe color transform, so libjpeg_turbo + // will guess YCbCr. + {"cs-uma-rgb-unknown-transform.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCbCr444}, + {"cs-uma-ycbcr-other.jpg", true, + BitmapImageMetrics::JpegColorSpace::kYCbCrOther}, + // Contains only 2 components. We expect the decode to fail and not produce + // any samples. + {"cs-uma-two-channels-jfif-marker.jpg", false}}; + +INSTANTIATE_TEST_CASE_P(JPEGImageDecoderTest, + ColorSpaceUMATest, + ::testing::ValuesIn(kColorSpaceUMATestParams)); + } // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc index f81c0fe0e9c..45f2b954174 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc +++ b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder.cc @@ -596,8 +596,10 @@ void PNGImageDecoder::RowAvailable(unsigned char* row_buffer, png_bytep row = row_buffer; if (png_bytep interlace_buffer = reader_->InterlaceBuffer()) { - unsigned color_channels = has_alpha ? 4 : 3; - row = interlace_buffer + (row_index * color_channels * Size().Width()); + unsigned bytes_per_pixel = has_alpha ? 4 : 3; + if (decode_to_half_float_) + bytes_per_pixel *= 2; + row = interlace_buffer + (row_index * bytes_per_pixel * Size().Width()); png_progressive_combine_row(reader_->PngPtr(), row, row_buffer); } diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc index 26ab25430ac..b4fa63f3c66 100644 --- a/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc +++ b/chromium/third_party/blink/renderer/platform/image-decoders/png/png_image_decoder_test.cc @@ -1078,6 +1078,11 @@ static void FillPNGSamplesSourcePixels(std::vector<PNGSample>& png_samples) { // Color components of opaque and transparent 16 bit PNG, read with libpng // in BigEndian and scaled to [0,1]. The values are read from non-interlaced // samples, but used for both interlaced and non-interlaced test cases. + // The sample pngs were all created by color converting the 8 bit sRGB source + // in Adobe Photoshop 18. The only exception is e-sRGB test case, for which + // Adobe software created a non-matching color profile (see crbug.com/874939). + // Hence, SkEncoder was used to generate the e-sRGB file (see the skia fiddle + // here: https://fiddle.skia.org/c/17beedfd66dac1ec930f0c414c50f847). static const std::vector<float> source_pixels_opaque_srgb = { 0.4986953536, 0.5826657511, 0.7013199054, 1, // Top left pixel 0.907988098, 0.8309605554, 0.492011902, 1, // Top right pixel @@ -1094,10 +1099,10 @@ static void FillPNGSamplesSourcePixels(std::vector<PNGSample>& png_samples) { 0.772121767, 0.9671625849, 0.973510338, 1, // Bottom left pixel 0.9118944076, 0.9645685512, 0.9110704204, 1}; // Bottom right pixel static const std::vector<float> source_pixels_opaque_e_srgb = { - 0.6414435035, 0.6857862211, 0.747005417, 1, // Top left pixel - 0.877347982, 0.8382848859, 0.6494087129, 1, // Top right pixel - 0.735194934, 0.9353933013, 0.9374380102, 1, // Bottom left pixel - 0.9209277485, 0.9575799191, 0.9264515145, 1}; // Bottom right pixel + 0.6977539062, 0.5839843750, 0.4978027344, 1, // Top left pixel + 0.4899902344, 0.8310546875, 0.9096679688, 1, // Top right pixel + 0.9760742188, 0.9721679688, 0.6230468750, 1, // Bottom left pixel + 0.9057617188, 0.9643554688, 0.8940429688, 1}; // Bottom right pixel static const std::vector<float> source_pixels_opaque_prophoto = { 0.5032883192, 0.5191271839, 0.6309147784, 1, // Top left pixel 0.8184176394, 0.8002899214, 0.5526970321, 1, // Top right pixel @@ -1125,10 +1130,10 @@ static void FillPNGSamplesSourcePixels(std::vector<PNGSample>& png_samples) { 0.4302738994, 0.9179064622, 0.933806363, 0.4, // Bottom left pixel 0.5595330739, 0.8228122377, 0.5554436561, 0.2}; // Bottom right pixel static const std::vector<float> source_pixels_transparent_e_srgb = { - 0.5517814908, 0.6072327764, 0.6837415122, 0.8, // Top left pixel - 0.7955901427, 0.7304646372, 0.4156557565, 0.6, // Top right pixel - 0.3380178531, 0.8385290303, 0.8435950256, 0.4, // Bottom left pixel - 0.6046997787, 0.7879606317, 0.6323186084, 0.2}; // Bottom right pixel + 0.6230468750, 0.4782714844, 0.3723144531, 0.8, // Top left pixel + 0.1528320312, 0.7172851562, 0.8466796875, 0.6, // Top right pixel + 0.9409179688, 0.9331054688, 0.0588073730, 0.4, // Bottom left pixel + 0.5253906250, 0.8310546875, 0.4743652344, 0.2}; // Bottom right pixel static const std::vector<float> source_pixels_transparent_prophoto = { 0.379064622, 0.3988708324, 0.5386282139, 0.8, // Top left pixel 0.6973525597, 0.6671396963, 0.2544289311, 0.6, // Top right pixel @@ -1174,10 +1179,8 @@ static void FillPNGSamplesSourcePixels(std::vector<PNGSample>& png_samples) { static std::vector<PNGSample> GetPNGSamplesInfo(bool include_8bit_pngs) { std::vector<PNGSample> png_samples; std::vector<String> interlace_status = {"", "_interlaced"}; - // TODO(zakerinasab) https://crbug.com/874939: - // e-sRGB decodes fine to 8888, but fails to decode to F16, hence not tested. - std::vector<String> color_spaces = {"sRGB", "AdobeRGB", "DisplayP3", - "ProPhoto", "Rec2020"}; + std::vector<String> color_spaces = {"sRGB", "AdobeRGB", "DisplayP3", + "e-sRGB", "ProPhoto", "Rec2020"}; std::vector<String> alpha_status = {"_opaque", "_transparent"}; for (String color_space : color_spaces) { diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/segment_stream.cc b/chromium/third_party/blink/renderer/platform/image-decoders/segment_stream.cc new file mode 100644 index 00000000000..b2af6860c0d --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/segment_stream.cc @@ -0,0 +1,102 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/platform/image-decoders/segment_stream.h" +#include <utility> +#include "third_party/blink/renderer/platform/image-decoders/segment_reader.h" + +namespace blink { + +SegmentStream::SegmentStream() = default; + +SegmentStream::SegmentStream(SegmentStream&& rhs) + : reader_(std::move(rhs.reader_)), position_(rhs.position_) {} + +SegmentStream& SegmentStream::operator=(SegmentStream&& rhs) { + reader_ = std::move(rhs.reader_); + position_ = rhs.position_; + + return *this; +} + +SegmentStream::~SegmentStream() = default; + +void SegmentStream::SetReader(scoped_refptr<SegmentReader> reader) { + reader_ = std::move(reader); +} + +bool SegmentStream::IsCleared() const { + return !reader_ || position_ > reader_->size(); +} + +size_t SegmentStream::read(void* buffer, size_t size) { + DCHECK(!IsCleared()); + + size = std::min(size, reader_->size() - position_); + + size_t bytes_advanced = 0; + if (!buffer) { // skipping, not reading + bytes_advanced = size; + } else { + bytes_advanced = peek(buffer, size); + } + + position_ += bytes_advanced; + + return bytes_advanced; +} + +size_t SegmentStream::peek(void* buffer, size_t size) const { + DCHECK(!IsCleared()); + + size = std::min(size, reader_->size() - position_); + + size_t total_bytes_peeked = 0; + char* buffer_as_char_ptr = reinterpret_cast<char*>(buffer); + while (size) { + const char* segment = nullptr; + size_t bytes_peeked = + reader_->GetSomeData(segment, position_ + total_bytes_peeked); + if (!bytes_peeked) + break; + if (bytes_peeked > size) + bytes_peeked = size; + + memcpy(buffer_as_char_ptr, segment, bytes_peeked); + buffer_as_char_ptr += bytes_peeked; + size -= bytes_peeked; + total_bytes_peeked += bytes_peeked; + } + + return total_bytes_peeked; +} + +bool SegmentStream::isAtEnd() const { + return !reader_ || position_ >= reader_->size(); +} + +bool SegmentStream::rewind() { + position_ = 0; + return true; +} + +bool SegmentStream::seek(size_t position) { + position_ = position; + return true; +} + +bool SegmentStream::move(long offset) { + DCHECK_GT(offset, 0); + position_ += offset; + return true; +} + +size_t SegmentStream::getLength() const { + if (reader_) + return reader_->size(); + + return 0; +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/segment_stream.h b/chromium/third_party/blink/renderer/platform/image-decoders/segment_stream.h new file mode 100644 index 00000000000..fa1ccf27571 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/segment_stream.h @@ -0,0 +1,51 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_SEGMENT_STREAM_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_IMAGE_DECODERS_SEGMENT_STREAM_H_ + +#include <algorithm> +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/skia/include/core/SkStream.h" + +namespace blink { + +class SegmentReader; + +class PLATFORM_EXPORT SegmentStream : public SkStream { + public: + SegmentStream(); + SegmentStream(const SegmentStream&) = delete; + SegmentStream& operator=(const SegmentStream&) = delete; + SegmentStream(SegmentStream&&); + SegmentStream& operator=(SegmentStream&&); + + ~SegmentStream() override; + + void SetReader(scoped_refptr<SegmentReader>); + // If a buffer has shrunk beyond the point we have read, it has been cleared. + // This allows clients to be aware of when data suddenly disappears. + bool IsCleared() const; + + // From SkStream: + size_t read(void* buffer, size_t) override; + size_t peek(void* buffer, size_t) const override; + bool isAtEnd() const override; + bool rewind() override; + bool hasPosition() const override { return true; } + size_t getPosition() const override { return position_; } + bool seek(size_t position) override; + bool move(long offset) override; + bool hasLength() const override { return true; } + size_t getLength() const override; + + private: + scoped_refptr<SegmentReader> reader_; + size_t position_ = 0; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/image-decoders/segment_stream_test.cc b/chromium/third_party/blink/renderer/platform/image-decoders/segment_stream_test.cc new file mode 100644 index 00000000000..c17f3bcb430 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/image-decoders/segment_stream_test.cc @@ -0,0 +1,698 @@ +// Copyright (c) 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "third_party/blink/renderer/platform/image-decoders/segment_stream.h" + +#include <array> +#include "base/memory/scoped_refptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/image-decoders/segment_reader.h" +#include "third_party/blink/renderer/platform/shared_buffer.h" + +// SegmentStream has 4 accessors which do not alter state: +// - isCleared() +// - isAtEnd() +// - getPosition() +// - getLength() +// +// For every operation which changes state we can test: +// - the operation completed as expected, +// - the accessors did not change, and/or +// - the accessors changed in the way we expected. +// +// There are actually 2 more accessors: +// - hasPosition() +// - hasLength() +// but these should always return true to indicate that we can call getLength() +// for example. So let's not add them to every state changing operation and add +// needless complexity. + +namespace blink { + +namespace { + +constexpr size_t kBufferAllocationSize = 20; +constexpr size_t kInsideBufferPosition = 10; +constexpr size_t kPastEndOfBufferPosition = 30; + +::testing::AssertionResult IsCleared(const SegmentStream&); +::testing::AssertionResult IsAtEnd(const SegmentStream&); +::testing::AssertionResult PositionIsZero(const SegmentStream&); +::testing::AssertionResult PositionIsInsideBuffer(const SegmentStream&); +::testing::AssertionResult PositionIsAtEndOfBuffer(const SegmentStream&); +::testing::AssertionResult LengthIsZero(const SegmentStream&); +::testing::AssertionResult LengthIsAllocationSize(const SegmentStream&); + +// Many of these tests require a SegmentStream with populated data. +// +// This function creates a buffer of size |kBufferAllocationSize| and prepares +// a SegmentStream with that buffer. +// This also populates other properties such as the length, cleared state, etc. +SegmentStream CreatePopulatedSegmentStream(); + +// This function creates a buffer of size |kBufferAllocationSize| to be used +// when populating a SegmentStream. +scoped_refptr<SegmentReader> CreateSegmentReader(); + +size_t ReadFromSegmentStream(SegmentStream&, + size_t amount_to_read = kInsideBufferPosition); +size_t PeekIntoSegmentStream(SegmentStream&, + size_t amount_to_peek = kInsideBufferPosition); + +} // namespace + +TEST(SegmentStreamTest, DefaultConstructorShouldSetIsCleared) { + SegmentStream segment_stream; + + ASSERT_TRUE(IsCleared(segment_stream)); +} + +TEST(SegmentStreamTest, DefaultConstructorShouldSetIsAtEnd) { + SegmentStream segment_stream; + + ASSERT_TRUE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, DefaultContructorShouldClearPosition) { + SegmentStream segment_stream; + + ASSERT_TRUE(PositionIsZero(segment_stream)); +} + +TEST(SegmentStreamTest, DefaultConstructorShouldHaveZeroLength) { + SegmentStream segment_stream; + + ASSERT_TRUE(LengthIsZero(segment_stream)); +} + +TEST(SegmentStreamTest, MoveConstructorShouldSetIsClearedWhenRhsIsCleared) { + SegmentStream cleared_segment_stream; + ASSERT_TRUE(IsCleared(cleared_segment_stream)); + + SegmentStream move_constructed_segment_stream = + std::move(cleared_segment_stream); + + ASSERT_TRUE(IsCleared(move_constructed_segment_stream)); +} + +TEST(SegmentStreamTest, + MoveConstructorShouldUnsetIsClearedWhenRhsIsNotCleared) { + SegmentStream uncleared_segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsCleared(uncleared_segment_stream)); + + SegmentStream move_constructed_segment_stream = + std::move(uncleared_segment_stream); + + ASSERT_FALSE(IsCleared(move_constructed_segment_stream)); +} + +TEST(SegmentStreamTest, MoveConstructorShouldSetIsAtEndWhenRhsIsAtEnd) { + SegmentStream at_end_segment_stream; + ASSERT_TRUE(IsAtEnd(at_end_segment_stream)); + + SegmentStream move_constructed_segment_stream = + std::move(at_end_segment_stream); + + ASSERT_TRUE(IsAtEnd(move_constructed_segment_stream)); +} + +TEST(SegmentStreamTest, MoveConstructorShouldUnsetIsAtEndWhenRhsIsNotAtEnd) { + SegmentStream not_at_end_segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsAtEnd(not_at_end_segment_stream)); + + SegmentStream move_constructed_segment_stream = + std::move(not_at_end_segment_stream); + + ASSERT_FALSE(IsAtEnd(move_constructed_segment_stream)); +} + +TEST(SegmentStreamTest, MoveContructorShouldCopyRhsPosition) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + segment_stream.seek(kInsideBufferPosition); + ASSERT_EQ(kInsideBufferPosition, segment_stream.getPosition()); + + SegmentStream move_constructed_segment_stream = std::move(segment_stream); + + ASSERT_EQ(kInsideBufferPosition, + move_constructed_segment_stream.getPosition()); +} + +TEST(SegmentStreamTest, MoveConstructorShouldCopyRhsLength) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength()); + + SegmentStream move_constructed_segment_stream = std::move(segment_stream); + + ASSERT_EQ(kBufferAllocationSize, move_constructed_segment_stream.getLength()); +} + +TEST(SegmentStreamTest, + MoveAssignmentOperatorShouldSetIsClearedWhenRhsIsCleared) { + SegmentStream cleared_segment_stream; + ASSERT_TRUE(IsCleared(cleared_segment_stream)); + + SegmentStream move_assigned_segment_stream; + move_assigned_segment_stream = std::move(cleared_segment_stream); + + ASSERT_TRUE(IsCleared(move_assigned_segment_stream)); +} + +TEST(SegmentStreamTest, + MoveAssignmentOperatorShouldUnsetIsClearedWhenRhsIsNotCleared) { + SegmentStream uncleared_segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsCleared(uncleared_segment_stream)); + + SegmentStream move_assigned_segment_stream; + move_assigned_segment_stream = std::move(uncleared_segment_stream); + + ASSERT_FALSE(IsCleared(move_assigned_segment_stream)); +} + +TEST(SegmentStreamTest, MoveAssignmentOperatorShouldSetIsAtEndWhenRhsIsAtEnd) { + SegmentStream at_end_segment_stream; + ASSERT_TRUE(IsAtEnd(at_end_segment_stream)); + + SegmentStream move_assigned_segment_stream; + move_assigned_segment_stream = std::move(at_end_segment_stream); + + ASSERT_TRUE(IsAtEnd(move_assigned_segment_stream)); +} + +TEST(SegmentStreamTest, + MoveAssignmentOperatorShouldUnsetIsAtEndWhenRhsIsNotAtEnd) { + SegmentStream not_at_end_segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsAtEnd(not_at_end_segment_stream)); + + SegmentStream move_assigned_segment_stream; + move_assigned_segment_stream = std::move(not_at_end_segment_stream); + + ASSERT_FALSE(IsAtEnd(move_assigned_segment_stream)); +} + +TEST(SegmentStreamTest, MoveAssignmentOperatorShouldCopyRhsPosition) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + segment_stream.seek(kInsideBufferPosition); + ASSERT_EQ(kInsideBufferPosition, segment_stream.getPosition()); + + SegmentStream move_assigned_segment_stream; + move_assigned_segment_stream = std::move(segment_stream); + + ASSERT_EQ(kInsideBufferPosition, move_assigned_segment_stream.getPosition()); +} + +TEST(SegmentStreamTest, MoveAssignmentOperatorShouldCopyRhsLength) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength()); + + SegmentStream move_assigned_segment_stream; + move_assigned_segment_stream = std::move(segment_stream); + + ASSERT_EQ(kBufferAllocationSize, move_assigned_segment_stream.getLength()); +} + +TEST(SegmentStreamTest, SetReaderShouldUnsetIsCleared) { + SegmentStream segment_stream; + scoped_refptr<SegmentReader> segment_reader = CreateSegmentReader(); + ASSERT_TRUE(IsCleared(segment_stream)); + + segment_stream.SetReader(segment_reader); + + ASSERT_FALSE(IsCleared(segment_stream)); +} + +TEST(SegmentStreamTest, SetReaderShouldUnsetIsAtEnd) { + SegmentStream segment_stream; + scoped_refptr<SegmentReader> segment_reader = CreateSegmentReader(); + ASSERT_TRUE(IsAtEnd(segment_stream)); + + segment_stream.SetReader(segment_reader); + + ASSERT_FALSE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, SetReaderShouldNotChangePosition) { + SegmentStream segment_stream; + scoped_refptr<SegmentReader> segment_reader = CreateSegmentReader(); + ASSERT_TRUE(PositionIsZero(segment_stream)); + + segment_stream.SetReader(segment_reader); + + ASSERT_TRUE(PositionIsZero(segment_stream)); +} + +TEST(SegmentStreamTest, SetReaderShouldUpdateLength) { + SegmentStream segment_stream; + scoped_refptr<SegmentReader> segment_reader = CreateSegmentReader(); + ASSERT_FALSE(LengthIsAllocationSize(segment_stream)); + + segment_stream.SetReader(segment_reader); + + ASSERT_TRUE(LengthIsAllocationSize(segment_stream)); +} + +TEST(SegmentStreamTest, SetReaderShouldSetIsClearedWhenSetToNull) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsCleared(segment_stream)); + + segment_stream.SetReader(nullptr); + + ASSERT_TRUE(IsCleared(segment_stream)); +} + +TEST(SegmentStreamTest, SetReaderShouldSetIsClearedWhenReaderSizeNotBigEnough) { + SegmentStream segment_stream; + segment_stream.seek(kPastEndOfBufferPosition); + scoped_refptr<SegmentReader> segment_reader = CreateSegmentReader(); + + segment_stream.SetReader(segment_reader); + + ASSERT_TRUE(IsCleared(segment_stream)); +} + +TEST(SegmentStreamTest, SetReaderShouldSetIsAtEndWhenReaderSizeNotBigEnough) { + SegmentStream segment_stream; + segment_stream.seek(kPastEndOfBufferPosition); + scoped_refptr<SegmentReader> segment_reader = CreateSegmentReader(); + + segment_stream.SetReader(segment_reader); + + ASSERT_TRUE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, + SetReaderShouldNotChangePositionWhenReaderSizeNotBigEnough) { + SegmentStream segment_stream; + segment_stream.seek(kPastEndOfBufferPosition); + scoped_refptr<SegmentReader> segment_reader = CreateSegmentReader(); + + segment_stream.SetReader(segment_reader); + + ASSERT_EQ(kPastEndOfBufferPosition, segment_stream.getPosition()); +} + +TEST(SegmentStreamTest, SetReaderShouldChangeLengthWhenReaderSizeNotBigEnough) { + SegmentStream segment_stream; + segment_stream.seek(kPastEndOfBufferPosition); + scoped_refptr<SegmentReader> segment_reader = CreateSegmentReader(); + + segment_stream.SetReader(segment_reader); + + ASSERT_TRUE(LengthIsAllocationSize(segment_stream)); +} + +TEST(SegmentStreamTest, SetReaderShouldSetIsAtEndWhenSetToNull) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsAtEnd(segment_stream)); + + segment_stream.SetReader(nullptr); + ASSERT_TRUE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, SetReaderShouldNotChangePositionWhenSetToNull) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + const size_t amount_read = ReadFromSegmentStream(segment_stream); + ASSERT_EQ(kInsideBufferPosition, amount_read); + const size_t pre_nulled_position = segment_stream.getPosition(); + ASSERT_EQ(kInsideBufferPosition, pre_nulled_position); + + segment_stream.SetReader(nullptr); + + ASSERT_EQ(kInsideBufferPosition, segment_stream.getPosition()); +} + +TEST(SegmentStreamTest, SetReaderShouldClearLengthWhenSetToNull) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(LengthIsZero(segment_stream)); + + segment_stream.SetReader(nullptr); + + ASSERT_TRUE(LengthIsZero(segment_stream)); +} + +TEST(SegmentStreamTest, ReadShouldConsumeBuffer) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + + const size_t amount_read = ReadFromSegmentStream(segment_stream); + + ASSERT_EQ(kInsideBufferPosition, amount_read); +} + +TEST(SegmentStreamTest, ReadShouldNotClear) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + + ReadFromSegmentStream(segment_stream); + + ASSERT_FALSE(IsCleared(segment_stream)); +} + +TEST(SegmentStreamTest, ReadShouldUpdateIsAtEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsAtEnd(segment_stream)); + + ReadFromSegmentStream(segment_stream, kBufferAllocationSize); + + ASSERT_TRUE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, ReadShouldUpdatePosition) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_TRUE(PositionIsZero(segment_stream)); + + ReadFromSegmentStream(segment_stream); + + ASSERT_TRUE(PositionIsInsideBuffer(segment_stream)); +} + +TEST(SegmentStreamTest, ReadShouldNotChangeLength) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength()); + + ReadFromSegmentStream(segment_stream); + + ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength()); +} + +TEST(SegmentStreamTest, ReadShouldConsumeBufferWithoutGoingPastTheEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + + const size_t amount_read = + ReadFromSegmentStream(segment_stream, kPastEndOfBufferPosition); + + ASSERT_EQ(kBufferAllocationSize, amount_read); +} + +TEST(SegmentStreamTest, ReadShouldSetIsAtEndWhenPastEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsAtEnd(segment_stream)); + + ReadFromSegmentStream(segment_stream, kPastEndOfBufferPosition); + + ASSERT_TRUE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, ReadShouldTruncatePositionWhenPastEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_TRUE(PositionIsZero(segment_stream)); + + ReadFromSegmentStream(segment_stream, kPastEndOfBufferPosition); + + ASSERT_TRUE(PositionIsAtEndOfBuffer(segment_stream)); +} + +TEST(SegmentStreamTest, PeekShouldConsumeBuffer) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + + const size_t amount_peeked = PeekIntoSegmentStream(segment_stream); + + ASSERT_EQ(kInsideBufferPosition, amount_peeked); +} + +TEST(SegmentStreamTest, PeekShouldNotClear) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + + PeekIntoSegmentStream(segment_stream); + + ASSERT_FALSE(IsCleared(segment_stream)); +} + +TEST(SegmentStreamTest, PeekShouldNotUpdateIsAtEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsAtEnd(segment_stream)); + + PeekIntoSegmentStream(segment_stream, kBufferAllocationSize); + + ASSERT_FALSE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, PeekShouldNotUpdatePosition) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_TRUE(PositionIsZero(segment_stream)); + + PeekIntoSegmentStream(segment_stream); + + ASSERT_TRUE(PositionIsZero(segment_stream)); +} + +TEST(SegmentStreamTest, PeekShouldNotChangeLength) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + + PeekIntoSegmentStream(segment_stream); + + ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength()); +} + +TEST(SegmentStreamTest, PeekShouldConsumeBufferWithoutGoingPastTheEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + + const size_t amount_peeked = + PeekIntoSegmentStream(segment_stream, kPastEndOfBufferPosition); + + ASSERT_EQ(kBufferAllocationSize, amount_peeked); +} + +TEST(SegmentStreamTest, PeekShouldNotSetIsAtEndWhenPastEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsAtEnd(segment_stream)); + + PeekIntoSegmentStream(segment_stream, kPastEndOfBufferPosition); + + ASSERT_FALSE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, PeekShouldNotTruncatePositionWhenPastEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_TRUE(PositionIsZero(segment_stream)); + + PeekIntoSegmentStream(segment_stream, kPastEndOfBufferPosition); + + ASSERT_TRUE(PositionIsZero(segment_stream)); +} + +TEST(SegmentStreamTest, RewindShouldNotClear) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ReadFromSegmentStream(segment_stream); + ASSERT_FALSE(IsCleared(segment_stream)); + + segment_stream.rewind(); + + ASSERT_FALSE(IsCleared(segment_stream)); +} + +TEST(SegmentStreamTest, RewindShouldNotSetAtEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ReadFromSegmentStream(segment_stream); + ASSERT_FALSE(IsAtEnd(segment_stream)); + + segment_stream.rewind(); + + ASSERT_FALSE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, RewindShouldResetPosition) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ReadFromSegmentStream(segment_stream); + ASSERT_TRUE(PositionIsInsideBuffer(segment_stream)); + + segment_stream.rewind(); + + ASSERT_TRUE(PositionIsZero(segment_stream)); +} + +TEST(SegmentStreamTest, RewindShouldNotChangeLength) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ReadFromSegmentStream(segment_stream); + + segment_stream.rewind(); + + ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength()); +} + +TEST(SegmentStreamTest, HasPositionShouldBeSupported) { + SegmentStream segment_stream; + + ASSERT_TRUE(segment_stream.hasPosition()); +} + +TEST(SegmentStreamTest, SeekShouldNotSetIsCleared) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsCleared(segment_stream)); + + segment_stream.seek(kInsideBufferPosition); + + ASSERT_FALSE(IsCleared(segment_stream)); +} + +TEST(SegmentStreamTest, SeekShouldNotSetIsAtEndWhenSeekingInsideTheBuffer) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsAtEnd(segment_stream)); + + segment_stream.seek(kInsideBufferPosition); + + ASSERT_FALSE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, SeekShouldSetIsAtEndWhenSeekingToTheEndOfTheBuffer) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_FALSE(IsAtEnd(segment_stream)); + + segment_stream.seek(kBufferAllocationSize); + + ASSERT_TRUE(IsAtEnd(segment_stream)); +} + +TEST(SegmentStreamTest, SeekShouldUpdatePosition) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_TRUE(PositionIsZero(segment_stream)); + + segment_stream.seek(kInsideBufferPosition); + + ASSERT_EQ(kInsideBufferPosition, segment_stream.getPosition()); +} + +TEST(SegmentStreamTest, SeekShouldNotTruncatePositionWhenPastEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_TRUE(PositionIsZero(segment_stream)); + + segment_stream.seek(kPastEndOfBufferPosition); + + ASSERT_EQ(kPastEndOfBufferPosition, segment_stream.getPosition()); +} + +TEST(SegmentStreamTest, SeekShouldNotUpdateLength) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + + segment_stream.seek(kInsideBufferPosition); + + ASSERT_EQ(kBufferAllocationSize, segment_stream.getLength()); +} + +TEST(SegmentStreamTest, MoveShouldNotSetCleared) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + + segment_stream.move(kInsideBufferPosition); + + ASSERT_FALSE(IsCleared(segment_stream)); +} + +TEST(SegmentStreamTest, MoveShouldUpdatePosition) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_TRUE(PositionIsZero(segment_stream)); + + segment_stream.move(kInsideBufferPosition); + + ASSERT_TRUE(PositionIsInsideBuffer(segment_stream)); +} + +TEST(SegmentStreamTest, MoveShouldNotTruncatePositionWhenPastEnd) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_TRUE(PositionIsZero(segment_stream)); + + segment_stream.move(kPastEndOfBufferPosition); + + ASSERT_EQ(kPastEndOfBufferPosition, segment_stream.getPosition()); +} + +TEST(SegmentStreamTest, MoveShouldNotChangeLength) { + SegmentStream segment_stream = CreatePopulatedSegmentStream(); + ASSERT_TRUE(LengthIsAllocationSize(segment_stream)); + + segment_stream.move(kInsideBufferPosition); + + ASSERT_TRUE(LengthIsAllocationSize(segment_stream)); +} + +TEST(SegmentStreamTest, HasLengthShouldBeSupported) { + SegmentStream segment_stream; + ASSERT_TRUE(segment_stream.hasLength()); +} + +namespace { + +::testing::AssertionResult IsCleared(const SegmentStream& segment_stream) { + if (segment_stream.IsCleared()) + return ::testing::AssertionSuccess(); + + return ::testing::AssertionFailure() << "SegmentStream is not clear"; +} + +::testing::AssertionResult IsAtEnd(const SegmentStream& segment_stream) { + if (segment_stream.isAtEnd()) + return ::testing::AssertionSuccess(); + + return ::testing::AssertionFailure() << "SegmentStream is not at the end"; +} + +::testing::AssertionResult PositionIsZero(const SegmentStream& segment_stream) { + if (segment_stream.getPosition() == 0ul) + return ::testing::AssertionSuccess(); + + return ::testing::AssertionFailure() << "SegmentStream position is not 0"; +} + +::testing::AssertionResult PositionIsInsideBuffer( + const SegmentStream& segment_stream) { + if (segment_stream.getPosition() == kInsideBufferPosition) + return ::testing::AssertionSuccess(); + + return ::testing::AssertionFailure() + << "SegmentStream position is not inside the buffer"; +} + +::testing::AssertionResult PositionIsAtEndOfBuffer( + const SegmentStream& segment_stream) { + if (segment_stream.getPosition() == kBufferAllocationSize) + return ::testing::AssertionSuccess(); + + return ::testing::AssertionFailure() + << "SegmentStream position is not at the end of the buffer"; +} + +::testing::AssertionResult LengthIsZero(const SegmentStream& segment_stream) { + if (segment_stream.getLength() == 0ul) + return ::testing::AssertionSuccess(); + + return ::testing::AssertionFailure() << "SegmentStream length is not 0"; +} + +::testing::AssertionResult LengthIsAllocationSize( + const SegmentStream& segment_stream) { + if (segment_stream.getLength() == kBufferAllocationSize) + return ::testing::AssertionSuccess(); + + return ::testing::AssertionFailure() + << "SegmentStream length is not the allocation size"; +} + +SegmentStream CreatePopulatedSegmentStream() { + SegmentStream segment_stream; + segment_stream.SetReader(CreateSegmentReader()); + return segment_stream; +} + +scoped_refptr<SegmentReader> CreateSegmentReader() { + std::array<char, kBufferAllocationSize> raw_buffer; + + scoped_refptr<SharedBuffer> shared_buffer = + SharedBuffer::Create(raw_buffer.data(), kBufferAllocationSize); + + scoped_refptr<SegmentReader> segment_reader = + SegmentReader::CreateFromSharedBuffer(std::move(shared_buffer)); + + return segment_reader; +} + +size_t ReadFromSegmentStream(SegmentStream& segment_stream, + size_t amount_to_read) { + std::array<char, kBufferAllocationSize> read_buffer; + return segment_stream.read(read_buffer.data(), amount_to_read); +} + +size_t PeekIntoSegmentStream(SegmentStream& segment_stream, + size_t amount_to_peek) { + std::array<char, kBufferAllocationSize> peek_buffer; + return segment_stream.peek(peek_buffer.data(), amount_to_peek); +} + +} // namespace +} // namespace blink |