Backed out 8 changesets (bug 1201796, bug 1196066) for mulet gij(28) failures CLOSED TREE
Backed out changeset 159d5d2946d3 (bug 1201796) Backed out changeset 958988218976 (bug 1201796) Backed out changeset 494e7553d641 (bug 1201796) Backed out changeset d58149411b7d (bug 1201796) Backed out changeset 35bd769b49f8 (bug 1196066) Backed out changeset e4f3d4279b4c (bug 1196066) Backed out changeset ca467297fa07 (bug 1196066) Backed out changeset b4851ce6637d (bug 1196066)
@ -220,16 +220,6 @@ ClippedImage::GetFrame(uint32_t aWhichFrame,
|
||||
return GetFrameInternal(mClip.Size(), Nothing(), aWhichFrame, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
ClippedImage::GetFrameAtSize(const IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
// XXX(seth): It'd be nice to support downscale-during-decode for this case,
|
||||
// but right now we just fall back to the intrinsic size.
|
||||
return GetFrame(aWhichFrame, aFlags);
|
||||
}
|
||||
|
||||
already_AddRefed<SourceSurface>
|
||||
ClippedImage::GetFrameInternal(const nsIntSize& aSize,
|
||||
const Maybe<SVGImageContext>& aSVGContext,
|
||||
|
@ -37,10 +37,6 @@ public:
|
||||
NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
|
||||
NS_IMETHOD_(already_AddRefed<SourceSurface>)
|
||||
GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
|
||||
NS_IMETHOD_(already_AddRefed<SourceSurface>)
|
||||
GetFrameAtSize(const gfx::IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
|
||||
|
@ -168,18 +168,10 @@ DynamicImage::GetFrame(uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
gfxIntSize size(mDrawable->Size());
|
||||
return GetFrameAtSize(IntSize(size.width, size.height),
|
||||
aWhichFrame,
|
||||
aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
DynamicImage::GetFrameAtSize(const IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
|
||||
CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8);
|
||||
CreateOffscreenContentDrawTarget(IntSize(size.width, size.height),
|
||||
SurfaceFormat::B8G8R8A8);
|
||||
if (!dt) {
|
||||
gfxWarning() <<
|
||||
"DynamicImage::GetFrame failed in CreateOffscreenContentDrawTarget";
|
||||
@ -187,7 +179,7 @@ DynamicImage::GetFrameAtSize(const IntSize& aSize,
|
||||
}
|
||||
nsRefPtr<gfxContext> context = new gfxContext(dt);
|
||||
|
||||
auto result = Draw(context, aSize, ImageRegion::Create(aSize),
|
||||
auto result = Draw(context, size, ImageRegion::Create(size),
|
||||
aWhichFrame, GraphicsFilter::FILTER_NEAREST,
|
||||
Nothing(), aFlags);
|
||||
|
||||
|
@ -44,14 +44,6 @@ FrozenImage::GetFrame(uint32_t aWhichFrame,
|
||||
return InnerImage()->GetFrame(FRAME_FIRST, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
FrozenImage::GetFrameAtSize(const IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
return InnerImage()->GetFrameAtSize(aSize, FRAME_FIRST, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(bool)
|
||||
FrozenImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
|
||||
{
|
||||
|
@ -37,10 +37,6 @@ public:
|
||||
NS_IMETHOD GetAnimated(bool* aAnimated) override;
|
||||
NS_IMETHOD_(already_AddRefed<SourceSurface>)
|
||||
GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
|
||||
NS_IMETHOD_(already_AddRefed<SourceSurface>)
|
||||
GetFrameAtSize(const gfx::IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
|
||||
|
@ -38,7 +38,6 @@ ShouldDownscaleDuringDecode(const nsCString& aMimeType)
|
||||
DecoderType type = DecoderFactory::GetDecoderType(aMimeType.get());
|
||||
return type == DecoderType::JPEG ||
|
||||
type == DecoderType::ICON ||
|
||||
type == DecoderType::ICO ||
|
||||
type == DecoderType::PNG ||
|
||||
type == DecoderType::BMP ||
|
||||
type == DecoderType::GIF;
|
||||
@ -143,13 +142,7 @@ ImageFactory::CreateAnonymousImage(const nsCString& aMimeType)
|
||||
newTracker->SetImage(newImage);
|
||||
newImage->SetProgressTracker(newTracker);
|
||||
|
||||
uint32_t imageFlags = Image::INIT_FLAG_SYNC_LOAD;
|
||||
if (gfxPrefs::ImageDownscaleDuringDecodeEnabled() &&
|
||||
ShouldDownscaleDuringDecode(aMimeType)) {
|
||||
imageFlags |= Image::INIT_FLAG_DOWNSCALE_DURING_DECODE;
|
||||
}
|
||||
|
||||
rv = newImage->Init(aMimeType.get(), imageFlags);
|
||||
rv = newImage->Init(aMimeType.get(), Image::INIT_FLAG_SYNC_LOAD);
|
||||
if (NS_FAILED(rv)) {
|
||||
return BadImage("RasterImage::Init failed", newImage);
|
||||
}
|
||||
|
@ -174,14 +174,6 @@ ImageWrapper::GetFrame(uint32_t aWhichFrame,
|
||||
return mInnerImage->GetFrame(aWhichFrame, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
ImageWrapper::GetFrameAtSize(const IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
return mInnerImage->GetFrameAtSize(aSize, aWhichFrame, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(bool)
|
||||
ImageWrapper::IsOpaque()
|
||||
{
|
||||
|
@ -122,16 +122,6 @@ OrientedImage::GetFrame(uint32_t aWhichFrame,
|
||||
return target->Snapshot();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
OrientedImage::GetFrameAtSize(const IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
// XXX(seth): It'd be nice to support downscale-during-decode for this case,
|
||||
// but right now we just fall back to the intrinsic size.
|
||||
return GetFrame(aWhichFrame, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(bool)
|
||||
OrientedImage::IsImageContainerAvailable(LayerManager* aManager, uint32_t aFlags)
|
||||
{
|
||||
|
@ -34,10 +34,6 @@ public:
|
||||
NS_IMETHOD GetIntrinsicRatio(nsSize* aRatio) override;
|
||||
NS_IMETHOD_(already_AddRefed<SourceSurface>)
|
||||
GetFrame(uint32_t aWhichFrame, uint32_t aFlags) override;
|
||||
NS_IMETHOD_(already_AddRefed<SourceSurface>)
|
||||
GetFrameAtSize(const gfx::IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(bool) IsImageContainerAvailable(layers::LayerManager* aManager,
|
||||
uint32_t aFlags) override;
|
||||
NS_IMETHOD_(already_AddRefed<layers::ImageContainer>)
|
||||
|
@ -698,28 +698,14 @@ NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
RasterImage::GetFrame(uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
return GetFrameInternal(mSize, aWhichFrame, aFlags).second().forget();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
RasterImage::GetFrameAtSize(const IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
return GetFrameInternal(aSize, aWhichFrame, aFlags).second().forget();
|
||||
return GetFrameInternal(aWhichFrame, aFlags).second().forget();
|
||||
}
|
||||
|
||||
Pair<DrawResult, RefPtr<SourceSurface>>
|
||||
RasterImage::GetFrameInternal(const IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
RasterImage::GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags)
|
||||
{
|
||||
MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
|
||||
|
||||
if (aSize.IsEmpty()) {
|
||||
return MakePair(DrawResult::BAD_ARGS, RefPtr<SourceSurface>());
|
||||
}
|
||||
|
||||
if (aWhichFrame > FRAME_MAX_VALUE) {
|
||||
return MakePair(DrawResult::BAD_ARGS, RefPtr<SourceSurface>());
|
||||
}
|
||||
@ -732,7 +718,7 @@ RasterImage::GetFrameInternal(const IntSize& aSize,
|
||||
// not waiting for the data to be loaded from the network or not passing
|
||||
// FLAG_SYNC_DECODE
|
||||
DrawableFrameRef frameRef =
|
||||
LookupFrame(GetRequestedFrameIndex(aWhichFrame), aSize, aFlags);
|
||||
LookupFrame(GetRequestedFrameIndex(aWhichFrame), mSize, aFlags);
|
||||
if (!frameRef) {
|
||||
// The OS threw this frame away and we couldn't redecode it.
|
||||
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
|
||||
@ -741,15 +727,15 @@ RasterImage::GetFrameInternal(const IntSize& aSize,
|
||||
// If this frame covers the entire image, we can just reuse its existing
|
||||
// surface.
|
||||
RefPtr<SourceSurface> frameSurf;
|
||||
if (!frameRef->NeedsPadding() &&
|
||||
frameRef->GetSize() == aSize) {
|
||||
IntRect frameRect = frameRef->GetRect();
|
||||
if (frameRect.x == 0 && frameRect.y == 0 &&
|
||||
frameRect.width == mSize.width &&
|
||||
frameRect.height == mSize.height) {
|
||||
frameSurf = frameRef->GetSurface();
|
||||
}
|
||||
|
||||
// The image doesn't have a usable surface because it's been optimized away or
|
||||
// because it's a partial update frame from an animation. Create one. (In this
|
||||
// case we fall back to returning a surface at our intrinsic size, even if a
|
||||
// different size was originally specified.)
|
||||
// because it's a partial update frame from an animation. Create one.
|
||||
if (!frameSurf) {
|
||||
frameSurf = CopyFrame(aWhichFrame, aFlags);
|
||||
}
|
||||
@ -770,7 +756,7 @@ RasterImage::GetCurrentImage(ImageContainer* aContainer, uint32_t aFlags)
|
||||
DrawResult drawResult;
|
||||
RefPtr<SourceSurface> surface;
|
||||
Tie(drawResult, surface) =
|
||||
GetFrameInternal(mSize, FRAME_CURRENT, aFlags | FLAG_ASYNC_NOTIFY);
|
||||
GetFrameInternal(FRAME_CURRENT, aFlags | FLAG_ASYNC_NOTIFY);
|
||||
if (!surface) {
|
||||
// The OS threw out some or all of our buffer. We'll need to wait for the
|
||||
// redecode (which was automatically triggered by GetFrame) to complete.
|
||||
|
@ -265,9 +265,7 @@ private:
|
||||
uint32_t aFlags);
|
||||
|
||||
Pair<DrawResult, RefPtr<gfx::SourceSurface>>
|
||||
GetFrameInternal(const gfx::IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags);
|
||||
GetFrameInternal(uint32_t aWhichFrame, uint32_t aFlags);
|
||||
|
||||
LookupResult LookupFrameInternal(uint32_t aFrameNum,
|
||||
const gfx::IntSize& aSize,
|
||||
|
@ -1,355 +0,0 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* StreamingLexer is a lexing framework designed to make it simple to write
|
||||
* image decoders without worrying about the details of how the data is arriving
|
||||
* from the network.
|
||||
*/
|
||||
|
||||
#ifndef mozilla_image_StreamingLexer_h
|
||||
#define mozilla_image_StreamingLexer_h
|
||||
|
||||
#include <algorithm>
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
#include "mozilla/Vector.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
/// Buffering behaviors for StreamingLexer transitions.
|
||||
enum class BufferingStrategy
|
||||
{
|
||||
BUFFERED, // Data will be buffered and processed in one chunk.
|
||||
UNBUFFERED // Data will be processed as it arrives, in multiple chunks.
|
||||
};
|
||||
|
||||
/// @return true if @aState is a terminal state.
|
||||
template <typename State>
|
||||
bool IsTerminalState(State aState)
|
||||
{
|
||||
return aState == State::SUCCESS ||
|
||||
aState == State::FAILURE;
|
||||
}
|
||||
|
||||
/**
|
||||
* LexerTransition is a type used to give commands to the lexing framework.
|
||||
* Code that uses StreamingLexer can create LexerTransition values using the
|
||||
* static methods on Transition, and then return them to the lexing framework
|
||||
* for execution.
|
||||
*/
|
||||
template <typename State>
|
||||
class LexerTransition
|
||||
{
|
||||
public:
|
||||
State NextState() const { return mNextState; }
|
||||
State UnbufferedState() const { return *mUnbufferedState; }
|
||||
size_t Size() const { return mSize; }
|
||||
BufferingStrategy Buffering() const { return mBufferingStrategy; }
|
||||
|
||||
private:
|
||||
friend struct Transition;
|
||||
|
||||
LexerTransition(const State& aNextState,
|
||||
const Maybe<State>& aUnbufferedState,
|
||||
size_t aSize,
|
||||
BufferingStrategy aBufferingStrategy)
|
||||
: mNextState(aNextState)
|
||||
, mUnbufferedState(aUnbufferedState)
|
||||
, mSize(aSize)
|
||||
, mBufferingStrategy(aBufferingStrategy)
|
||||
{
|
||||
MOZ_ASSERT_IF(mBufferingStrategy == BufferingStrategy::UNBUFFERED,
|
||||
mUnbufferedState);
|
||||
MOZ_ASSERT_IF(mUnbufferedState,
|
||||
mBufferingStrategy == BufferingStrategy::UNBUFFERED);
|
||||
}
|
||||
|
||||
State mNextState;
|
||||
Maybe<State> mUnbufferedState;
|
||||
size_t mSize;
|
||||
BufferingStrategy mBufferingStrategy;
|
||||
};
|
||||
|
||||
struct Transition
|
||||
{
|
||||
/// Transition to @aNextState, buffering @aSize bytes of data.
|
||||
template <typename State>
|
||||
static LexerTransition<State>
|
||||
To(const State& aNextState, size_t aSize)
|
||||
{
|
||||
MOZ_ASSERT(!IsTerminalState(aNextState));
|
||||
return LexerTransition<State>(aNextState, Nothing(), aSize,
|
||||
BufferingStrategy::BUFFERED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to @aNextState via @aUnbufferedState, reading @aSize bytes of
|
||||
* data unbuffered.
|
||||
*
|
||||
* The unbuffered data will be delivered in state @aUnbufferedState, which may
|
||||
* be invoked repeatedly until all @aSize bytes have been delivered. Then,
|
||||
* @aNextState will be invoked with no data. No state transitions are allowed
|
||||
* from @aUnbufferedState except for transitions to a terminal state, so
|
||||
* @aNextState will always be reached unless lexing terminates early.
|
||||
*/
|
||||
template <typename State>
|
||||
static LexerTransition<State>
|
||||
ToUnbuffered(const State& aNextState,
|
||||
const State& aUnbufferedState,
|
||||
size_t aSize)
|
||||
{
|
||||
MOZ_ASSERT(!IsTerminalState(aNextState));
|
||||
MOZ_ASSERT(!IsTerminalState(aUnbufferedState));
|
||||
return LexerTransition<State>(aNextState, Some(aUnbufferedState), aSize,
|
||||
BufferingStrategy::UNBUFFERED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue receiving unbuffered data. @aUnbufferedState should be the same
|
||||
* state as the @aUnbufferedState specified in the preceding call to
|
||||
* ToUnbuffered().
|
||||
*
|
||||
* This should be used during an unbuffered read initiated by ToUnbuffered().
|
||||
*/
|
||||
template <typename State>
|
||||
static LexerTransition<State>
|
||||
ContinueUnbuffered(const State& aUnbufferedState)
|
||||
{
|
||||
MOZ_ASSERT(!IsTerminalState(aUnbufferedState));
|
||||
return LexerTransition<State>(aUnbufferedState, Nothing(), 0,
|
||||
BufferingStrategy::BUFFERED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Terminate lexing, ending up in terminal state @aFinalState.
|
||||
*
|
||||
* No more data will be delivered after Terminate() is used.
|
||||
*/
|
||||
template <typename State>
|
||||
static LexerTransition<State>
|
||||
Terminate(const State& aFinalState)
|
||||
{
|
||||
MOZ_ASSERT(IsTerminalState(aFinalState));
|
||||
return LexerTransition<State>(aFinalState, Nothing(), 0,
|
||||
BufferingStrategy::BUFFERED);
|
||||
}
|
||||
|
||||
private:
|
||||
Transition();
|
||||
};
|
||||
|
||||
/**
|
||||
* StreamingLexer is a lexing framework designed to make it simple to write
|
||||
* image decoders without worrying about the details of how the data is arriving
|
||||
* from the network.
|
||||
*
|
||||
* To use StreamingLexer:
|
||||
*
|
||||
* - Create a State type. This should be an |enum class| listing all of the
|
||||
* states that you can be in while lexing the image format you're trying to
|
||||
* read. It must contain the two terminal states SUCCESS and FAILURE.
|
||||
*
|
||||
* - Add an instance of StreamingLexer<State> to your decoder class. Initialize
|
||||
* it with a Transition::To() the state that you want to start lexing in.
|
||||
*
|
||||
* - In your decoder's WriteInternal method(), call Lex(), passing in the input
|
||||
* data and length that are passed to WriteInternal(). You also need to pass
|
||||
* a lambda which dispatches to lexing code for each state based on the State
|
||||
* value that's passed in. The lambda generally should just continue a
|
||||
* |switch| statement that calls different methods for each State value. Each
|
||||
* method should return a LexerTransition<State>, which the lambda should
|
||||
* return in turn.
|
||||
*
|
||||
* - Write the methods that actually implement lexing for your image format.
|
||||
* These methods should return either Transition::To(), to move on to another
|
||||
* state, or Transition::Terminate(), if lexing has terminated in either
|
||||
* success or failure. (There are also additional transitions for unbuffered
|
||||
* reads; see below.)
|
||||
*
|
||||
* That's all there is to it. The StreamingLexer will track your position in the
|
||||
* input and buffer enough data so that your lexing methods can process
|
||||
* everything in one pass. Lex() returns Nothing() if more data is needed, in
|
||||
* which case you should just return from WriteInternal(). If lexing reaches a
|
||||
* terminal state, Lex() returns Some(State::SUCCESS) or Some(State::FAILURE),
|
||||
* and you can check which one to determine if lexing succeeded or failed and do
|
||||
* any necessary cleanup.
|
||||
*
|
||||
* There's one more wrinkle: some lexers may want to *avoid* buffering in some
|
||||
* cases, and just process the data as it comes in. This is useful if, for
|
||||
* example, you just want to skip over a large section of data; there's no point
|
||||
* in buffering data you're just going to ignore.
|
||||
*
|
||||
* You can begin an unbuffered read with Transition::ToUnbuffered(). This works
|
||||
* a little differently than Transition::To() in that you specify *two* states.
|
||||
* The @aUnbufferedState argument specifies a state that will be called
|
||||
* repeatedly with unbuffered data, as soon as it arrives. The implementation
|
||||
* for that state should return either a transition to a terminal state, or
|
||||
* Transition::ContinueUnbuffered(). Once the amount of data requested in the
|
||||
* original call to Transition::ToUnbuffered() has been delivered, Lex() will
|
||||
* transition to the @aNextState state specified via Transition::ToUnbuffered().
|
||||
* That state will be invoked with *no* data; it's just called to signal that
|
||||
* the unbuffered read is over.
|
||||
*
|
||||
* XXX(seth): We should be able to get of the |State| stuff totally once bug
|
||||
* 1198451 lands, since we can then just return a function representing the next
|
||||
* state directly.
|
||||
*/
|
||||
template <typename State, size_t InlineBufferSize = 16>
|
||||
class StreamingLexer
|
||||
{
|
||||
public:
|
||||
explicit StreamingLexer(LexerTransition<State> aStartState)
|
||||
: mTransition(aStartState)
|
||||
, mToReadUnbuffered(0)
|
||||
{ }
|
||||
|
||||
template <typename Func>
|
||||
Maybe<State> Lex(const char* aInput, size_t aLength, Func aFunc)
|
||||
{
|
||||
if (IsTerminalState(mTransition.NextState())) {
|
||||
// We've already reached a terminal state. We never deliver any more data
|
||||
// in this case; just return the terminal state again immediately.
|
||||
return Some(mTransition.NextState());
|
||||
}
|
||||
|
||||
if (mToReadUnbuffered > 0) {
|
||||
// We're continuing an unbuffered read.
|
||||
|
||||
MOZ_ASSERT(mBuffer.empty(),
|
||||
"Shouldn't be continuing an unbuffered read and a buffered "
|
||||
"read at the same time");
|
||||
|
||||
size_t toRead = std::min(mToReadUnbuffered, aLength);
|
||||
|
||||
// Call aFunc with the unbuffered state to indicate that we're in the middle
|
||||
// of an unbuffered read. We enforce that any state transition passed back
|
||||
// to us is either a terminal states or takes us back to the unbuffered
|
||||
// state.
|
||||
LexerTransition<State> unbufferedTransition =
|
||||
aFunc(mTransition.UnbufferedState(), aInput, toRead);
|
||||
if (IsTerminalState(unbufferedTransition.NextState())) {
|
||||
mTransition = unbufferedTransition;
|
||||
return Some(mTransition.NextState()); // Done!
|
||||
}
|
||||
MOZ_ASSERT(mTransition.UnbufferedState() ==
|
||||
unbufferedTransition.NextState());
|
||||
|
||||
aInput += toRead;
|
||||
aLength -= toRead;
|
||||
mToReadUnbuffered -= toRead;
|
||||
if (mToReadUnbuffered != 0) {
|
||||
return Nothing(); // Need more input.
|
||||
}
|
||||
|
||||
// We're done with the unbuffered read, so transition to the next state.
|
||||
mTransition = aFunc(mTransition.NextState(), nullptr, 0);
|
||||
if (IsTerminalState(mTransition.NextState())) {
|
||||
return Some(mTransition.NextState()); // Done!
|
||||
}
|
||||
} else if (0 < mBuffer.length()) {
|
||||
// We're continuing a buffered read.
|
||||
|
||||
MOZ_ASSERT(mToReadUnbuffered == 0,
|
||||
"Shouldn't be continuing an unbuffered read and a buffered "
|
||||
"read at the same time");
|
||||
MOZ_ASSERT(mBuffer.length() < mTransition.Size(),
|
||||
"Buffered more than we needed?");
|
||||
|
||||
size_t toRead = std::min(aLength, mTransition.Size() - mBuffer.length());
|
||||
|
||||
mBuffer.append(aInput, toRead);
|
||||
aInput += toRead;
|
||||
aLength -= toRead;
|
||||
if (mBuffer.length() != mTransition.Size()) {
|
||||
return Nothing(); // Need more input.
|
||||
}
|
||||
|
||||
// We've buffered everything, so transition to the next state.
|
||||
mTransition =
|
||||
aFunc(mTransition.NextState(), mBuffer.begin(), mBuffer.length());
|
||||
mBuffer.clear();
|
||||
if (IsTerminalState(mTransition.NextState())) {
|
||||
return Some(mTransition.NextState()); // Done!
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_ASSERT(mToReadUnbuffered == 0);
|
||||
MOZ_ASSERT(mBuffer.empty());
|
||||
|
||||
// Process states as long as we continue to have enough input to do so.
|
||||
while (mTransition.Size() <= aLength) {
|
||||
size_t toRead = mTransition.Size();
|
||||
|
||||
if (mTransition.Buffering() == BufferingStrategy::BUFFERED) {
|
||||
mTransition = aFunc(mTransition.NextState(), aInput, toRead);
|
||||
} else {
|
||||
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::UNBUFFERED);
|
||||
|
||||
// Call aFunc with the unbuffered state to indicate that we're in the
|
||||
// middle of an unbuffered read. We enforce that any state transition
|
||||
// passed back to us is either a terminal states or takes us back to the
|
||||
// unbuffered state.
|
||||
LexerTransition<State> unbufferedTransition =
|
||||
aFunc(mTransition.UnbufferedState(), aInput, toRead);
|
||||
if (IsTerminalState(unbufferedTransition.NextState())) {
|
||||
mTransition = unbufferedTransition;
|
||||
return Some(mTransition.NextState()); // Done!
|
||||
}
|
||||
MOZ_ASSERT(mTransition.UnbufferedState() ==
|
||||
unbufferedTransition.NextState());
|
||||
|
||||
// We're done with the unbuffered read, so transition to the next state.
|
||||
mTransition = aFunc(mTransition.NextState(), nullptr, 0);
|
||||
}
|
||||
|
||||
aInput += toRead;
|
||||
aLength -= toRead;
|
||||
|
||||
if (IsTerminalState(mTransition.NextState())) {
|
||||
return Some(mTransition.NextState()); // Done!
|
||||
}
|
||||
}
|
||||
|
||||
if (aLength == 0) {
|
||||
// We finished right at a transition point. Just wait for more data.
|
||||
return Nothing();
|
||||
}
|
||||
|
||||
// If the next state is unbuffered, deliver what we can and then wait.
|
||||
if (mTransition.Buffering() == BufferingStrategy::UNBUFFERED) {
|
||||
LexerTransition<State> unbufferedTransition =
|
||||
aFunc(mTransition.UnbufferedState(), aInput, aLength);
|
||||
if (IsTerminalState(unbufferedTransition.NextState())) {
|
||||
mTransition = unbufferedTransition;
|
||||
return Some(mTransition.NextState()); // Done!
|
||||
}
|
||||
MOZ_ASSERT(mTransition.UnbufferedState() ==
|
||||
unbufferedTransition.NextState());
|
||||
|
||||
mToReadUnbuffered = mTransition.Size() - aLength;
|
||||
return Nothing(); // Need more input.
|
||||
}
|
||||
|
||||
// If the next state is buffered, buffer what we can and then wait.
|
||||
MOZ_ASSERT(mTransition.Buffering() == BufferingStrategy::BUFFERED);
|
||||
if (!mBuffer.reserve(mTransition.Size())) {
|
||||
return Some(State::FAILURE); // Done due to allocation failure.
|
||||
}
|
||||
mBuffer.append(aInput, aLength);
|
||||
return Nothing(); // Need more input.
|
||||
}
|
||||
|
||||
private:
|
||||
Vector<char, InlineBufferSize> mBuffer;
|
||||
LexerTransition<State> mTransition;
|
||||
size_t mToReadUnbuffered;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_image_StreamingLexer_h
|
@ -668,8 +668,19 @@ VectorImage::IsOpaque()
|
||||
/* [noscript] SourceSurface getFrame(in uint32_t aWhichFrame,
|
||||
* in uint32_t aFlags; */
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
VectorImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags)
|
||||
VectorImage::GetFrame(uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
|
||||
|
||||
if (aWhichFrame > FRAME_MAX_VALUE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mError || !mIsFullyLoaded) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Look up height & width
|
||||
// ----------------------
|
||||
SVGSVGElement* svgElem = mSVGDocumentWrapper->GetRootSVGElem();
|
||||
@ -684,32 +695,12 @@ VectorImage::GetFrame(uint32_t aWhichFrame, uint32_t aFlags)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return GetFrameAtSize(imageIntSize, aWhichFrame, aFlags);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(already_AddRefed<SourceSurface>)
|
||||
VectorImage::GetFrameAtSize(const IntSize& aSize,
|
||||
uint32_t aWhichFrame,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE);
|
||||
|
||||
if (aSize.IsEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (aWhichFrame > FRAME_MAX_VALUE) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mError || !mIsFullyLoaded) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Make our surface the size of what will ultimately be drawn to it.
|
||||
// (either the full image size, or the restricted region)
|
||||
RefPtr<DrawTarget> dt = gfxPlatform::GetPlatform()->
|
||||
CreateOffscreenContentDrawTarget(aSize, SurfaceFormat::B8G8R8A8);
|
||||
CreateOffscreenContentDrawTarget(IntSize(imageIntSize.width,
|
||||
imageIntSize.height),
|
||||
SurfaceFormat::B8G8R8A8);
|
||||
if (!dt) {
|
||||
NS_ERROR("Could not create a DrawTarget");
|
||||
return nullptr;
|
||||
@ -717,8 +708,8 @@ VectorImage::GetFrameAtSize(const IntSize& aSize,
|
||||
|
||||
nsRefPtr<gfxContext> context = new gfxContext(dt);
|
||||
|
||||
auto result = Draw(context, aSize,
|
||||
ImageRegion::Create(aSize),
|
||||
auto result = Draw(context, imageIntSize,
|
||||
ImageRegion::Create(imageIntSize),
|
||||
aWhichFrame, GraphicsFilter::FILTER_NEAREST,
|
||||
Nothing(), aFlags);
|
||||
|
||||
|
@ -14,14 +14,13 @@
|
||||
|
||||
#include "RasterImage.h"
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
// Constants.
|
||||
static const uint32_t ICOHEADERSIZE = 6;
|
||||
static const uint32_t BITMAPINFOSIZE = 40;
|
||||
#define ICONCOUNTOFFSET 4
|
||||
#define DIRENTRYOFFSET 6
|
||||
#define BITMAPINFOSIZE 40
|
||||
#define PREFICONSIZE 16
|
||||
|
||||
// ----------------------------------------
|
||||
// Actual Data Processing
|
||||
@ -58,31 +57,21 @@ nsICODecoder::GetNumColors()
|
||||
return numColors;
|
||||
}
|
||||
|
||||
|
||||
nsICODecoder::nsICODecoder(RasterImage* aImage)
|
||||
: Decoder(aImage)
|
||||
, mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE))
|
||||
, mBiggestResourceColorDepth(0)
|
||||
, mBestResourceDelta(INT_MIN)
|
||||
, mBestResourceColorDepth(0)
|
||||
, mNumIcons(0)
|
||||
, mCurrIcon(0)
|
||||
, mBPP(0)
|
||||
, mMaskRowSize(0)
|
||||
, mCurrMaskLine(0)
|
||||
{ }
|
||||
|
||||
nsresult
|
||||
nsICODecoder::SetTargetSize(const nsIntSize& aSize)
|
||||
: Decoder(aImage)
|
||||
{
|
||||
// Make sure the size is reasonable.
|
||||
if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0;
|
||||
mIsPNG = false;
|
||||
mRow = nullptr;
|
||||
mOldLine = mCurLine = 1; // Otherwise decoder will never start
|
||||
}
|
||||
|
||||
nsICODecoder::~nsICODecoder()
|
||||
{
|
||||
if (mRow) {
|
||||
free(mRow);
|
||||
}
|
||||
|
||||
// Create a downscaler that we'll filter our output through.
|
||||
mDownscaler.emplace(aSize);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
@ -91,6 +80,11 @@ nsICODecoder::FinishInternal()
|
||||
// We shouldn't be called in error cases
|
||||
MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
|
||||
|
||||
// Finish the internally used decoder as well.
|
||||
if (mContainedDecoder && !mContainedDecoder->HasError()) {
|
||||
mContainedDecoder->FinishInternal();
|
||||
}
|
||||
|
||||
GetFinalStateFromContainedDecoder();
|
||||
}
|
||||
|
||||
@ -107,9 +101,6 @@ nsICODecoder::GetFinalStateFromContainedDecoder()
|
||||
return;
|
||||
}
|
||||
|
||||
// Finish the internally used decoder.
|
||||
mContainedDecoder->CompleteDecode();
|
||||
|
||||
mDecodeDone = mContainedDecoder->GetDecodeDone();
|
||||
mDataError = mDataError || mContainedDecoder->HasDataError();
|
||||
mFailCode = NS_SUCCEEDED(mFailCode) ? mContainedDecoder->GetDecoderError()
|
||||
@ -118,8 +109,6 @@ nsICODecoder::GetFinalStateFromContainedDecoder()
|
||||
mProgress |= mContainedDecoder->TakeProgress();
|
||||
mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
|
||||
mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
|
||||
|
||||
MOZ_ASSERT(HasError() || !mCurrentFrame || mCurrentFrame->IsImageComplete());
|
||||
}
|
||||
|
||||
// Returns a buffer filled with the bitmap file header in little endian:
|
||||
@ -217,11 +206,10 @@ nsICODecoder::FixBitmapWidth(int8_t* bih)
|
||||
}
|
||||
|
||||
// The BMP information header's bits per pixel should be trusted
|
||||
// more than what we have. Usually the ICO's BPP is set to 0.
|
||||
// more than what we have. Usually the ICO's BPP is set to 0
|
||||
int32_t
|
||||
nsICODecoder::ReadBPP(const char* aBIH)
|
||||
nsICODecoder::ExtractBPPFromBitmap(int8_t* bih)
|
||||
{
|
||||
const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
|
||||
int32_t bitsPerPixel;
|
||||
memcpy(&bitsPerPixel, bih + 14, sizeof(bitsPerPixel));
|
||||
NativeEndian::swapFromLittleEndianInPlace(&bitsPerPixel, 1);
|
||||
@ -229,395 +217,22 @@ nsICODecoder::ReadBPP(const char* aBIH)
|
||||
}
|
||||
|
||||
int32_t
|
||||
nsICODecoder::ReadBIHSize(const char* aBIH)
|
||||
nsICODecoder::ExtractBIHSizeFromBitmap(int8_t* bih)
|
||||
{
|
||||
const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
|
||||
int32_t headerSize;
|
||||
memcpy(&headerSize, bih, sizeof(headerSize));
|
||||
NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1);
|
||||
return headerSize;
|
||||
}
|
||||
|
||||
LexerTransition<ICOState>
|
||||
nsICODecoder::ReadHeader(const char* aData)
|
||||
void
|
||||
nsICODecoder::SetHotSpotIfCursor()
|
||||
{
|
||||
// If the third byte is 1, this is an icon. If 2, a cursor.
|
||||
if ((aData[2] != 1) && (aData[2] != 2)) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
mIsCursor = (aData[2] == 2);
|
||||
|
||||
// The fifth and sixth bytes specify the number of resources in the file.
|
||||
mNumIcons =
|
||||
LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aData + 4));
|
||||
if (mNumIcons == 0) {
|
||||
return Transition::Terminate(ICOState::SUCCESS); // Nothing to do.
|
||||
if (!mIsCursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Downscale-during-decode can end up decoding different resources in the ICO
|
||||
// file depending on the target size. Since the resources are not necessarily
|
||||
// scaled versions of the same image, some may be transparent and some may not
|
||||
// be. We could be precise about transparency if we decoded the metadata of
|
||||
// every resource, but for now we don't and it's safest to assume that
|
||||
// transparency could be present.
|
||||
PostHasTransparency();
|
||||
|
||||
return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
|
||||
}
|
||||
|
||||
size_t
|
||||
nsICODecoder::FirstResourceOffset() const
|
||||
{
|
||||
MOZ_ASSERT(mNumIcons > 0,
|
||||
"Calling FirstResourceOffset before processing header");
|
||||
|
||||
// The first resource starts right after the directory, which starts right
|
||||
// after the ICO header.
|
||||
return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE;
|
||||
}
|
||||
|
||||
LexerTransition<ICOState>
|
||||
nsICODecoder::ReadDirEntry(const char* aData)
|
||||
{
|
||||
mCurrIcon++;
|
||||
|
||||
// Read the directory entry.
|
||||
IconDirEntry e;
|
||||
memset(&e, 0, sizeof(e));
|
||||
memcpy(&e.mWidth, aData, sizeof(e.mWidth));
|
||||
memcpy(&e.mHeight, aData + 1, sizeof(e.mHeight));
|
||||
memcpy(&e.mColorCount, aData + 2, sizeof(e.mColorCount));
|
||||
memcpy(&e.mReserved, aData + 3, sizeof(e.mReserved));
|
||||
memcpy(&e.mPlanes, aData + 4, sizeof(e.mPlanes));
|
||||
e.mPlanes = LittleEndian::readUint16(&e.mPlanes);
|
||||
memcpy(&e.mBitCount, aData + 6, sizeof(e.mBitCount));
|
||||
e.mBitCount = LittleEndian::readUint16(&e.mBitCount);
|
||||
memcpy(&e.mBytesInRes, aData + 8, sizeof(e.mBytesInRes));
|
||||
e.mBytesInRes = LittleEndian::readUint32(&e.mBytesInRes);
|
||||
memcpy(&e.mImageOffset, aData + 12, sizeof(e.mImageOffset));
|
||||
e.mImageOffset = LittleEndian::readUint32(&e.mImageOffset);
|
||||
|
||||
// Determine if this is the biggest resource we've seen so far. We always use
|
||||
// the biggest resource for the intrinsic size, and if we're not downscaling,
|
||||
// we select it as the best resource as well.
|
||||
IntSize entrySize(GetRealWidth(e), GetRealHeight(e));
|
||||
if (e.mBitCount >= mBiggestResourceColorDepth &&
|
||||
entrySize.width * entrySize.height >=
|
||||
mBiggestResourceSize.width * mBiggestResourceSize.height) {
|
||||
mBiggestResourceSize = entrySize;
|
||||
mBiggestResourceColorDepth = e.mBitCount;
|
||||
mBiggestResourceHotSpot = IntSize(e.mXHotspot, e.mYHotspot);
|
||||
|
||||
if (!mDownscaler) {
|
||||
mDirEntry = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (mDownscaler) {
|
||||
// Calculate the delta between this resource's size and the desired size, so
|
||||
// we can see if it is better than our current-best option. In the case of
|
||||
// several equally-good resources, we use the last one. "Better" in this
|
||||
// case is determined by |delta|, a measure of the difference in size
|
||||
// between the entry we've found and the downscaler's target size. We will
|
||||
// choose the smallest resource that is >= the target size (i.e. we assume
|
||||
// it's better to downscale a larger icon than to upscale a smaller one).
|
||||
IntSize desiredSize = mDownscaler->TargetSize();
|
||||
int32_t delta = entrySize.width - desiredSize.width +
|
||||
entrySize.height - desiredSize.height;
|
||||
if (e.mBitCount >= mBestResourceColorDepth &&
|
||||
((mBestResourceDelta < 0 && delta >= mBestResourceDelta) ||
|
||||
(delta >= 0 && delta <= mBestResourceDelta))) {
|
||||
mBestResourceDelta = delta;
|
||||
mBestResourceColorDepth = e.mBitCount;
|
||||
mDirEntry = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (mCurrIcon == mNumIcons) {
|
||||
// Ensure the resource we selected has an offset past the ICO headers.
|
||||
if (mDirEntry.mImageOffset < FirstResourceOffset()) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
// If this is a cursor, set the hotspot. We use the hotspot from the biggest
|
||||
// resource since we also use that resource for the intrinsic size.
|
||||
if (mIsCursor) {
|
||||
mImageMetadata.SetHotspot(mBiggestResourceHotSpot.width,
|
||||
mBiggestResourceHotSpot.height);
|
||||
}
|
||||
|
||||
// We always report the biggest resource's size as the intrinsic size; this
|
||||
// is necessary for downscale-during-decode to work since we won't even
|
||||
// attempt to *upscale* while decoding.
|
||||
PostSize(mBiggestResourceSize.width, mBiggestResourceSize.height);
|
||||
if (IsMetadataDecode()) {
|
||||
return Transition::Terminate(ICOState::SUCCESS);
|
||||
}
|
||||
|
||||
// If the resource we selected matches the downscaler's target size
|
||||
// perfectly, we don't need to do any downscaling.
|
||||
if (mDownscaler && GetRealSize() == mDownscaler->TargetSize()) {
|
||||
mDownscaler.reset();
|
||||
}
|
||||
|
||||
size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset();
|
||||
return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
|
||||
ICOState::SKIP_TO_RESOURCE,
|
||||
offsetToResource);
|
||||
}
|
||||
|
||||
return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
|
||||
}
|
||||
|
||||
LexerTransition<ICOState>
|
||||
nsICODecoder::SniffResource(const char* aData)
|
||||
{
|
||||
// We use the first PNGSIGNATURESIZE bytes to determine whether this resource
|
||||
// is a PNG or a BMP.
|
||||
bool isPNG = !memcmp(aData, nsPNGDecoder::pngSignatureBytes,
|
||||
PNGSIGNATURESIZE);
|
||||
if (isPNG) {
|
||||
// Create a PNG decoder which will do the rest of the work for us.
|
||||
mContainedDecoder = new nsPNGDecoder(mImage);
|
||||
mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
|
||||
mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
|
||||
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
|
||||
if (mDownscaler) {
|
||||
mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
|
||||
}
|
||||
mContainedDecoder->Init();
|
||||
|
||||
if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
// Read in the rest of the PNG unbuffered.
|
||||
size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE;
|
||||
return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
|
||||
ICOState::READ_PNG,
|
||||
toRead);
|
||||
} else {
|
||||
// Create a BMP decoder which will do most of the work for us; the exception
|
||||
// is the AND mask, which isn't present in standalone BMPs.
|
||||
nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
|
||||
mContainedDecoder = bmpDecoder;
|
||||
bmpDecoder->SetUseAlphaData(true);
|
||||
mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
|
||||
mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
|
||||
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
|
||||
if (mDownscaler) {
|
||||
mContainedDecoder->SetTargetSize(mDownscaler->TargetSize());
|
||||
}
|
||||
mContainedDecoder->Init();
|
||||
|
||||
// Make sure we have a sane size for the bitmap information header.
|
||||
int32_t bihSize = ReadBIHSize(aData);
|
||||
if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
// Buffer the first part of the bitmap information header.
|
||||
memcpy(mBIHraw, aData, PNGSIGNATURESIZE);
|
||||
|
||||
// Read in the rest of the bitmap information header.
|
||||
return Transition::To(ICOState::READ_BIH,
|
||||
BITMAPINFOSIZE - PNGSIGNATURESIZE);
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<ICOState>
|
||||
nsICODecoder::ReadPNG(const char* aData, uint32_t aLen)
|
||||
{
|
||||
if (!WriteToContainedDecoder(aData, aLen)) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
// Raymond Chen says that 32bpp only are valid PNG ICOs
|
||||
// http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
|
||||
if (!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
return Transition::ContinueUnbuffered(ICOState::READ_PNG);
|
||||
}
|
||||
|
||||
LexerTransition<ICOState>
|
||||
nsICODecoder::ReadBIH(const char* aData)
|
||||
{
|
||||
// Buffer the rest of the bitmap information header.
|
||||
memcpy(mBIHraw + PNGSIGNATURESIZE, aData, BITMAPINFOSIZE - PNGSIGNATURESIZE);
|
||||
|
||||
// Extracting the BPP from the BIH header; it should be trusted over the one
|
||||
// we have from the ICO header.
|
||||
mBPP = ReadBPP(mBIHraw);
|
||||
|
||||
// The ICO format when containing a BMP does not include the 14 byte
|
||||
// bitmap file header. To use the code of the BMP decoder we need to
|
||||
// generate this header ourselves and feed it to the BMP decoder.
|
||||
int8_t bfhBuffer[BMPFILEHEADERSIZE];
|
||||
if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
if (!WriteToContainedDecoder(reinterpret_cast<const char*>(bfhBuffer),
|
||||
sizeof(bfhBuffer))) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
// Fix the ICO height from the BIH. It needs to be halved so our BMP decoder
|
||||
// will understand, because the BMP decoder doesn't expect the alpha mask that
|
||||
// follows the BMP data in an ICO.
|
||||
if (!FixBitmapHeight(reinterpret_cast<int8_t*>(mBIHraw))) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
// Fix the ICO width from the BIH.
|
||||
if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
// Write out the BMP's bitmap info header.
|
||||
if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
// Sometimes the ICO BPP header field is not filled out so we should trust the
|
||||
// contained resource over our own information.
|
||||
// XXX(seth): Is this ever different than the value we obtained from
|
||||
// ReadBPP() above?
|
||||
nsRefPtr<nsBMPDecoder> bmpDecoder =
|
||||
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
|
||||
mBPP = bmpDecoder->GetBitsPerPixel();
|
||||
|
||||
// Check to make sure we have valid color settings.
|
||||
uint16_t numColors = GetNumColors();
|
||||
if (numColors == uint16_t(-1)) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
// Do we have an AND mask on this BMP? If so, we need to read it after we read
|
||||
// the BMP data itself.
|
||||
uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors;
|
||||
bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry.mBytesInRes;
|
||||
ICOState afterBMPState = hasANDMask ? ICOState::PREPARE_FOR_MASK
|
||||
: ICOState::FINISHED_RESOURCE;
|
||||
|
||||
// Read in the rest of the BMP unbuffered.
|
||||
return Transition::ToUnbuffered(afterBMPState,
|
||||
ICOState::READ_BMP,
|
||||
bmpDataLength);
|
||||
}
|
||||
|
||||
LexerTransition<ICOState>
|
||||
nsICODecoder::ReadBMP(const char* aData, uint32_t aLen)
|
||||
{
|
||||
if (!WriteToContainedDecoder(aData, aLen)) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
return Transition::ContinueUnbuffered(ICOState::READ_BMP);
|
||||
}
|
||||
|
||||
LexerTransition<ICOState>
|
||||
nsICODecoder::PrepareForMask()
|
||||
{
|
||||
nsRefPtr<nsBMPDecoder> bmpDecoder =
|
||||
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
|
||||
|
||||
uint16_t numColors = GetNumColors();
|
||||
MOZ_ASSERT(numColors != uint16_t(-1));
|
||||
|
||||
// Determine the length of the AND mask.
|
||||
uint32_t bmpLengthWithHeader =
|
||||
BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors;
|
||||
MOZ_ASSERT(bmpLengthWithHeader < mDirEntry.mBytesInRes);
|
||||
uint32_t maskLength = mDirEntry.mBytesInRes - bmpLengthWithHeader;
|
||||
|
||||
// If we have a 32-bpp BMP with alpha data, we ignore the AND mask. We can
|
||||
// also obviously ignore it if the image has zero width or zero height.
|
||||
if ((bmpDecoder->GetBitsPerPixel() == 32 && bmpDecoder->HasAlphaData()) ||
|
||||
GetRealWidth() == 0 || GetRealHeight() == 0) {
|
||||
return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
|
||||
ICOState::SKIP_MASK,
|
||||
maskLength);
|
||||
}
|
||||
|
||||
// Compute the row size for the mask.
|
||||
mMaskRowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
|
||||
|
||||
// If the expected size of the AND mask is larger than its actual size, then
|
||||
// we must have a truncated (and therefore corrupt) AND mask.
|
||||
uint32_t expectedLength = mMaskRowSize * GetRealHeight();
|
||||
if (maskLength < expectedLength) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
mCurrMaskLine = GetRealHeight();
|
||||
return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
|
||||
}
|
||||
|
||||
|
||||
LexerTransition<ICOState>
|
||||
nsICODecoder::ReadMaskRow(const char* aData)
|
||||
{
|
||||
mCurrMaskLine--;
|
||||
|
||||
nsRefPtr<nsBMPDecoder> bmpDecoder =
|
||||
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
|
||||
|
||||
uint32_t* imageData = bmpDecoder->GetImageData();
|
||||
if (!imageData) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
uint8_t sawTransparency = 0;
|
||||
uint32_t* decoded = imageData + mCurrMaskLine * GetRealWidth();
|
||||
uint32_t* decodedRowEnd = decoded + GetRealWidth();
|
||||
const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData);
|
||||
const uint8_t* maskRowEnd = mask + mMaskRowSize;
|
||||
|
||||
// Iterate simultaneously through the AND mask and the image data.
|
||||
while (mask < maskRowEnd) {
|
||||
uint8_t idx = *mask++;
|
||||
sawTransparency |= idx;
|
||||
for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) {
|
||||
// Clear pixel completely for transparency.
|
||||
if (idx & bit) {
|
||||
*decoded = 0;
|
||||
}
|
||||
decoded++;
|
||||
}
|
||||
}
|
||||
|
||||
// If any bits are set in sawTransparency, then we know at least one pixel was
|
||||
// transparent.
|
||||
if (sawTransparency) {
|
||||
PostHasTransparency();
|
||||
bmpDecoder->SetHasAlphaData();
|
||||
}
|
||||
|
||||
if (mCurrMaskLine == 0) {
|
||||
return Transition::To(ICOState::FINISHED_RESOURCE, 0);
|
||||
}
|
||||
|
||||
return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
|
||||
}
|
||||
|
||||
LexerTransition<ICOState>
|
||||
nsICODecoder::FinishResource()
|
||||
{
|
||||
// Make sure the actual size of the resource matches the size in the directory
|
||||
// entry. If not, we consider the image corrupt.
|
||||
if (mContainedDecoder->HasSize() &&
|
||||
mContainedDecoder->GetSize() != GetRealSize()) {
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
}
|
||||
|
||||
return Transition::Terminate(ICOState::SUCCESS);
|
||||
mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
|
||||
}
|
||||
|
||||
void
|
||||
@ -627,50 +242,369 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
|
||||
MOZ_ASSERT(aBuffer);
|
||||
MOZ_ASSERT(aCount > 0);
|
||||
|
||||
Maybe<ICOState> terminalState =
|
||||
mLexer.Lex(aBuffer, aCount,
|
||||
[=](ICOState aState, const char* aData, size_t aLength) {
|
||||
switch (aState) {
|
||||
case ICOState::HEADER:
|
||||
return ReadHeader(aData);
|
||||
case ICOState::DIR_ENTRY:
|
||||
return ReadDirEntry(aData);
|
||||
case ICOState::SKIP_TO_RESOURCE:
|
||||
return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE);
|
||||
case ICOState::FOUND_RESOURCE:
|
||||
return Transition::To(ICOState::SNIFF_RESOURCE, PNGSIGNATURESIZE);
|
||||
case ICOState::SNIFF_RESOURCE:
|
||||
return SniffResource(aData);
|
||||
case ICOState::READ_PNG:
|
||||
return ReadPNG(aData, aLength);
|
||||
case ICOState::READ_BIH:
|
||||
return ReadBIH(aData);
|
||||
case ICOState::READ_BMP:
|
||||
return ReadBMP(aData, aLength);
|
||||
case ICOState::PREPARE_FOR_MASK:
|
||||
return PrepareForMask();
|
||||
case ICOState::READ_MASK_ROW:
|
||||
return ReadMaskRow(aData);
|
||||
case ICOState::SKIP_MASK:
|
||||
return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
|
||||
case ICOState::FINISHED_RESOURCE:
|
||||
return FinishResource();
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("Unknown ICOState");
|
||||
return Transition::Terminate(ICOState::FAILURE);
|
||||
while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons.
|
||||
if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor
|
||||
if ((*aBuffer != 1) && (*aBuffer != 2)) {
|
||||
PostDataError();
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
if (!terminalState) {
|
||||
return; // Need more data.
|
||||
mIsCursor = (*aBuffer == 2);
|
||||
}
|
||||
mPos++; aBuffer++; aCount--;
|
||||
}
|
||||
|
||||
if (*terminalState == ICOState::FAILURE) {
|
||||
PostDataError();
|
||||
if (mPos == ICONCOUNTOFFSET && aCount >= 2) {
|
||||
mNumIcons =
|
||||
LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aBuffer));
|
||||
aBuffer += 2;
|
||||
mPos += 2;
|
||||
aCount -= 2;
|
||||
}
|
||||
|
||||
if (mNumIcons == 0) {
|
||||
return; // Nothing to do.
|
||||
}
|
||||
|
||||
uint16_t colorDepth = 0;
|
||||
|
||||
// If we didn't get a #-moz-resolution, default to PREFICONSIZE.
|
||||
if (mResolution.width == 0 && mResolution.height == 0) {
|
||||
mResolution.SizeTo(PREFICONSIZE, PREFICONSIZE);
|
||||
}
|
||||
|
||||
// A measure of the difference in size between the entry we've found
|
||||
// and the requested size. We will choose the smallest image that is
|
||||
// >= requested size (i.e. we assume it's better to downscale a larger
|
||||
// icon than to upscale a smaller one).
|
||||
int32_t diff = INT_MIN;
|
||||
|
||||
// Loop through each entry's dir entry
|
||||
while (mCurrIcon < mNumIcons) {
|
||||
if (mPos >= DIRENTRYOFFSET + (mCurrIcon * sizeof(mDirEntryArray)) &&
|
||||
mPos < DIRENTRYOFFSET + ((mCurrIcon + 1) * sizeof(mDirEntryArray))) {
|
||||
uint32_t toCopy = sizeof(mDirEntryArray) -
|
||||
(mPos - DIRENTRYOFFSET - mCurrIcon *
|
||||
sizeof(mDirEntryArray));
|
||||
if (toCopy > aCount) {
|
||||
toCopy = aCount;
|
||||
}
|
||||
memcpy(mDirEntryArray + sizeof(mDirEntryArray) - toCopy, aBuffer, toCopy);
|
||||
mPos += toCopy;
|
||||
aCount -= toCopy;
|
||||
aBuffer += toCopy;
|
||||
}
|
||||
if (aCount == 0) {
|
||||
return; // Need more data
|
||||
}
|
||||
|
||||
IconDirEntry e;
|
||||
if (mPos == (DIRENTRYOFFSET + ICODIRENTRYSIZE) +
|
||||
(mCurrIcon * sizeof(mDirEntryArray))) {
|
||||
mCurrIcon++;
|
||||
ProcessDirEntry(e);
|
||||
// We can't use GetRealWidth and GetRealHeight here because those operate
|
||||
// on mDirEntry, here we are going through each item in the directory.
|
||||
// Calculate the delta between this image's size and the desired size,
|
||||
// so we can see if it is better than our current-best option.
|
||||
// In the case of several equally-good images, we use the last one.
|
||||
int32_t delta = (e.mWidth == 0 ? 256 : e.mWidth) - mResolution.width +
|
||||
(e.mHeight == 0 ? 256 : e.mHeight) - mResolution.height;
|
||||
if (e.mBitCount >= colorDepth &&
|
||||
((diff < 0 && delta >= diff) || (delta >= 0 && delta <= diff))) {
|
||||
diff = delta;
|
||||
mImageOffset = e.mImageOffset;
|
||||
|
||||
// ensure mImageOffset is >= size of the direntry headers (bug #245631)
|
||||
uint32_t minImageOffset = DIRENTRYOFFSET +
|
||||
mNumIcons * sizeof(mDirEntryArray);
|
||||
if (mImageOffset < minImageOffset) {
|
||||
PostDataError();
|
||||
return;
|
||||
}
|
||||
|
||||
colorDepth = e.mBitCount;
|
||||
memcpy(&mDirEntry, &e, sizeof(IconDirEntry));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mPos < mImageOffset) {
|
||||
// Skip to (or at least towards) the desired image offset
|
||||
uint32_t toSkip = mImageOffset - mPos;
|
||||
if (toSkip > aCount) {
|
||||
toSkip = aCount;
|
||||
}
|
||||
|
||||
mPos += toSkip;
|
||||
aBuffer += toSkip;
|
||||
aCount -= toSkip;
|
||||
}
|
||||
|
||||
// If we are within the first PNGSIGNATURESIZE bytes of the image data,
|
||||
// then we have either a BMP or a PNG. We use the first PNGSIGNATURESIZE
|
||||
// bytes to determine which one we have.
|
||||
if (mCurrIcon == mNumIcons && mPos >= mImageOffset &&
|
||||
mPos < mImageOffset + PNGSIGNATURESIZE) {
|
||||
uint32_t toCopy = PNGSIGNATURESIZE - (mPos - mImageOffset);
|
||||
if (toCopy > aCount) {
|
||||
toCopy = aCount;
|
||||
}
|
||||
|
||||
memcpy(mSignature + (mPos - mImageOffset), aBuffer, toCopy);
|
||||
mPos += toCopy;
|
||||
aCount -= toCopy;
|
||||
aBuffer += toCopy;
|
||||
|
||||
mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
|
||||
PNGSIGNATURESIZE);
|
||||
if (mIsPNG) {
|
||||
mContainedDecoder = new nsPNGDecoder(mImage);
|
||||
mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
|
||||
mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
|
||||
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
|
||||
mContainedDecoder->Init();
|
||||
if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a PNG, let the PNG decoder do all of the rest of the work
|
||||
if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
|
||||
if (!WriteToContainedDecoder(aBuffer, aCount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!HasSize() && mContainedDecoder->HasSize()) {
|
||||
nsIntSize size = mContainedDecoder->GetSize();
|
||||
PostSize(size.width, size.height);
|
||||
}
|
||||
|
||||
mPos += aCount;
|
||||
aBuffer += aCount;
|
||||
aCount = 0;
|
||||
|
||||
// Raymond Chen says that 32bpp only are valid PNG ICOs
|
||||
// http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
|
||||
if (!IsMetadataDecode() &&
|
||||
!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
|
||||
PostDataError();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(*terminalState == ICOState::SUCCESS);
|
||||
// We've processed all of the icon dir entries and are within the
|
||||
// bitmap info size
|
||||
if (!mIsPNG && mCurrIcon == mNumIcons && mPos >= mImageOffset &&
|
||||
mPos >= mImageOffset + PNGSIGNATURESIZE &&
|
||||
mPos < mImageOffset + BITMAPINFOSIZE) {
|
||||
|
||||
// As we were decoding, we did not know if we had a PNG signature or the
|
||||
// start of a bitmap information header. At this point we know we had
|
||||
// a bitmap information header and not a PNG signature, so fill the bitmap
|
||||
// information header with the data it should already have.
|
||||
memcpy(mBIHraw, mSignature, PNGSIGNATURESIZE);
|
||||
|
||||
// We've found the icon.
|
||||
uint32_t toCopy = sizeof(mBIHraw) - (mPos - mImageOffset);
|
||||
if (toCopy > aCount) {
|
||||
toCopy = aCount;
|
||||
}
|
||||
|
||||
memcpy(mBIHraw + (mPos - mImageOffset), aBuffer, toCopy);
|
||||
mPos += toCopy;
|
||||
aCount -= toCopy;
|
||||
aBuffer += toCopy;
|
||||
}
|
||||
|
||||
// If we have a BMP inside the ICO and we have read the BIH header
|
||||
if (!mIsPNG && mPos == mImageOffset + BITMAPINFOSIZE) {
|
||||
|
||||
// Make sure we have a sane value for the bitmap information header
|
||||
int32_t bihSize = ExtractBIHSizeFromBitmap(reinterpret_cast<int8_t*>
|
||||
(mBIHraw));
|
||||
if (bihSize != BITMAPINFOSIZE) {
|
||||
PostDataError();
|
||||
return;
|
||||
}
|
||||
// We are extracting the BPP from the BIH header as it should be trusted
|
||||
// over the one we have from the icon header
|
||||
mBPP = ExtractBPPFromBitmap(reinterpret_cast<int8_t*>(mBIHraw));
|
||||
|
||||
// Init the bitmap decoder which will do most of the work for us
|
||||
// It will do everything except the AND mask which isn't present in bitmaps
|
||||
// bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
|
||||
nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
|
||||
mContainedDecoder = bmpDecoder;
|
||||
bmpDecoder->SetUseAlphaData(true);
|
||||
mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
|
||||
mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
|
||||
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
|
||||
mContainedDecoder->Init();
|
||||
|
||||
// The ICO format when containing a BMP does not include the 14 byte
|
||||
// bitmap file header. To use the code of the BMP decoder we need to
|
||||
// generate this header ourselves and feed it to the BMP decoder.
|
||||
int8_t bfhBuffer[BMPFILEHEADERSIZE];
|
||||
if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
|
||||
PostDataError();
|
||||
return;
|
||||
}
|
||||
if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup the cursor hot spot if one is present
|
||||
SetHotSpotIfCursor();
|
||||
|
||||
// Fix the ICO height from the BIH.
|
||||
// Fix the height on the BIH to be /2 so our BMP decoder will understand.
|
||||
if (!FixBitmapHeight(reinterpret_cast<int8_t*>(mBIHraw))) {
|
||||
PostDataError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix the ICO width from the BIH.
|
||||
if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
|
||||
PostDataError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Write out the BMP's bitmap info header
|
||||
if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsIntSize size = mContainedDecoder->GetSize();
|
||||
PostSize(size.width, size.height);
|
||||
|
||||
// We have the size. If we're doing a metadata decode, we're done.
|
||||
if (IsMetadataDecode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sometimes the ICO BPP header field is not filled out
|
||||
// so we should trust the contained resource over our own
|
||||
// information.
|
||||
mBPP = bmpDecoder->GetBitsPerPixel();
|
||||
|
||||
// Check to make sure we have valid color settings
|
||||
uint16_t numColors = GetNumColors();
|
||||
if (numColors == (uint16_t)-1) {
|
||||
PostDataError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have a BMP
|
||||
if (!mIsPNG && mContainedDecoder && mPos >= mImageOffset + BITMAPINFOSIZE) {
|
||||
uint16_t numColors = GetNumColors();
|
||||
if (numColors == (uint16_t)-1) {
|
||||
PostDataError();
|
||||
return;
|
||||
}
|
||||
// Feed the actual image data (not including headers) into the BMP decoder
|
||||
uint32_t bmpDataOffset = mDirEntry.mImageOffset + BITMAPINFOSIZE;
|
||||
uint32_t bmpDataEnd = mDirEntry.mImageOffset + BITMAPINFOSIZE +
|
||||
static_cast<nsBMPDecoder*>(mContainedDecoder.get())->
|
||||
GetCompressedImageSize() +
|
||||
4 * numColors;
|
||||
|
||||
// If we are feeding in the core image data, but we have not yet
|
||||
// reached the ICO's 'AND buffer mask'
|
||||
if (mPos >= bmpDataOffset && mPos < bmpDataEnd) {
|
||||
|
||||
// Figure out how much data the BMP decoder wants
|
||||
uint32_t toFeed = bmpDataEnd - mPos;
|
||||
if (toFeed > aCount) {
|
||||
toFeed = aCount;
|
||||
}
|
||||
|
||||
if (!WriteToContainedDecoder(aBuffer, toFeed)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPos += toFeed;
|
||||
aCount -= toFeed;
|
||||
aBuffer += toFeed;
|
||||
}
|
||||
|
||||
// If the bitmap is fully processed, treat any left over data as the ICO's
|
||||
// 'AND buffer mask' which appears after the bitmap resource.
|
||||
if (!mIsPNG && mPos >= bmpDataEnd) {
|
||||
nsRefPtr<nsBMPDecoder> bmpDecoder =
|
||||
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
|
||||
|
||||
// There may be an optional AND bit mask after the data. This is
|
||||
// only used if the alpha data is not already set. The alpha data
|
||||
// is used for 32bpp bitmaps as per the comment in ICODecoder.h
|
||||
// The alpha mask should be checked in all other cases.
|
||||
if (bmpDecoder->GetBitsPerPixel() != 32 || !bmpDecoder->HasAlphaData()) {
|
||||
uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
|
||||
if (mPos == bmpDataEnd) {
|
||||
mPos++;
|
||||
mRowBytes = 0;
|
||||
mCurLine = GetRealHeight();
|
||||
mRow = (uint8_t*)realloc(mRow, rowSize);
|
||||
if (!mRow) {
|
||||
PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure memory has been allocated before decoding.
|
||||
MOZ_ASSERT(mRow, "mRow is null");
|
||||
if (!mRow) {
|
||||
PostDataError();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t sawTransparency = 0;
|
||||
|
||||
while (mCurLine > 0 && aCount > 0) {
|
||||
uint32_t toCopy = std::min(rowSize - mRowBytes, aCount);
|
||||
if (toCopy) {
|
||||
memcpy(mRow + mRowBytes, aBuffer, toCopy);
|
||||
aCount -= toCopy;
|
||||
aBuffer += toCopy;
|
||||
mRowBytes += toCopy;
|
||||
}
|
||||
if (rowSize == mRowBytes) {
|
||||
mCurLine--;
|
||||
mRowBytes = 0;
|
||||
|
||||
uint32_t* imageData = bmpDecoder->GetImageData();
|
||||
if (!imageData) {
|
||||
PostDataError();
|
||||
return;
|
||||
}
|
||||
uint32_t* decoded = imageData + mCurLine * GetRealWidth();
|
||||
uint32_t* decoded_end = decoded + GetRealWidth();
|
||||
uint8_t* p = mRow;
|
||||
uint8_t* p_end = mRow + rowSize;
|
||||
while (p < p_end) {
|
||||
uint8_t idx = *p++;
|
||||
sawTransparency |= idx;
|
||||
for (uint8_t bit = 0x80; bit && decoded<decoded_end; bit >>= 1) {
|
||||
// Clear pixel completely for transparency.
|
||||
if (idx & bit) {
|
||||
*decoded = 0;
|
||||
}
|
||||
decoded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If any bits are set in sawTransparency, then we know at least one
|
||||
// pixel was transparent.
|
||||
if (sawTransparency) {
|
||||
PostHasTransparency();
|
||||
bmpDecoder->SetHasAlphaData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
@ -680,7 +614,7 @@ nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
|
||||
mProgress |= mContainedDecoder->TakeProgress();
|
||||
mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
|
||||
if (mContainedDecoder->HasDataError()) {
|
||||
PostDataError();
|
||||
mDataError = mContainedDecoder->HasDataError();
|
||||
}
|
||||
if (mContainedDecoder->HasDecoderError()) {
|
||||
PostDecoderError(mContainedDecoder->GetDecoderError());
|
||||
@ -688,5 +622,24 @@ nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
|
||||
return !HasError();
|
||||
}
|
||||
|
||||
void
|
||||
nsICODecoder::ProcessDirEntry(IconDirEntry& aTarget)
|
||||
{
|
||||
memset(&aTarget, 0, sizeof(aTarget));
|
||||
memcpy(&aTarget.mWidth, mDirEntryArray, sizeof(aTarget.mWidth));
|
||||
memcpy(&aTarget.mHeight, mDirEntryArray + 1, sizeof(aTarget.mHeight));
|
||||
memcpy(&aTarget.mColorCount, mDirEntryArray + 2, sizeof(aTarget.mColorCount));
|
||||
memcpy(&aTarget.mReserved, mDirEntryArray + 3, sizeof(aTarget.mReserved));
|
||||
memcpy(&aTarget.mPlanes, mDirEntryArray + 4, sizeof(aTarget.mPlanes));
|
||||
aTarget.mPlanes = LittleEndian::readUint16(&aTarget.mPlanes);
|
||||
memcpy(&aTarget.mBitCount, mDirEntryArray + 6, sizeof(aTarget.mBitCount));
|
||||
aTarget.mBitCount = LittleEndian::readUint16(&aTarget.mBitCount);
|
||||
memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes));
|
||||
aTarget.mBytesInRes = LittleEndian::readUint32(&aTarget.mBytesInRes);
|
||||
memcpy(&aTarget.mImageOffset, mDirEntryArray + 12,
|
||||
sizeof(aTarget.mImageOffset));
|
||||
aTarget.mImageOffset = LittleEndian::readUint32(&aTarget.mImageOffset);
|
||||
}
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
@ -8,7 +8,6 @@
|
||||
#define mozilla_image_decoders_nsICODecoder_h
|
||||
|
||||
#include "nsAutoPtr.h"
|
||||
#include "StreamingLexer.h"
|
||||
#include "Decoder.h"
|
||||
#include "imgFrame.h"
|
||||
#include "nsBMPDecoder.h"
|
||||
@ -20,58 +19,28 @@ namespace image {
|
||||
|
||||
class RasterImage;
|
||||
|
||||
enum class ICOState
|
||||
{
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
HEADER,
|
||||
DIR_ENTRY,
|
||||
SKIP_TO_RESOURCE,
|
||||
FOUND_RESOURCE,
|
||||
SNIFF_RESOURCE,
|
||||
READ_PNG,
|
||||
READ_BIH,
|
||||
READ_BMP,
|
||||
PREPARE_FOR_MASK,
|
||||
READ_MASK_ROW,
|
||||
SKIP_MASK,
|
||||
FINISHED_RESOURCE
|
||||
};
|
||||
|
||||
class nsICODecoder : public Decoder
|
||||
{
|
||||
public:
|
||||
virtual ~nsICODecoder() { }
|
||||
virtual ~nsICODecoder();
|
||||
|
||||
nsresult SetTargetSize(const nsIntSize& aSize) override;
|
||||
|
||||
/// @return the width of the icon directory entry @aEntry.
|
||||
static uint32_t GetRealWidth(const IconDirEntry& aEntry)
|
||||
// Obtains the width of the icon directory entry
|
||||
uint32_t GetRealWidth() const
|
||||
{
|
||||
return aEntry.mWidth == 0 ? 256 : aEntry.mWidth;
|
||||
return mDirEntry.mWidth == 0 ? 256 : mDirEntry.mWidth;
|
||||
}
|
||||
|
||||
/// @return the width of the selected directory entry (mDirEntry).
|
||||
uint32_t GetRealWidth() const { return GetRealWidth(mDirEntry); }
|
||||
|
||||
/// @return the height of the icon directory entry @aEntry.
|
||||
static uint32_t GetRealHeight(const IconDirEntry& aEntry)
|
||||
// Obtains the height of the icon directory entry
|
||||
uint32_t GetRealHeight() const
|
||||
{
|
||||
return aEntry.mHeight == 0 ? 256 : aEntry.mHeight;
|
||||
return mDirEntry.mHeight == 0 ? 256 : mDirEntry.mHeight;
|
||||
}
|
||||
|
||||
/// @return the height of the selected directory entry (mDirEntry).
|
||||
uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); }
|
||||
|
||||
/// @return the size of the selected directory entry (mDirEntry).
|
||||
gfx::IntSize GetRealSize() const
|
||||
virtual void SetResolution(const gfx::IntSize& aResolution) override
|
||||
{
|
||||
return gfx::IntSize(GetRealWidth(), GetRealHeight());
|
||||
mResolution = aResolution;
|
||||
}
|
||||
|
||||
/// @return The offset from the beginning of the ICO to the first resource.
|
||||
size_t FirstResourceOffset() const;
|
||||
|
||||
virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
|
||||
virtual void FinishInternal() override;
|
||||
virtual void FinishWithErrorInternal() override;
|
||||
@ -89,6 +58,10 @@ private:
|
||||
// Gets decoder state from the contained decoder so it's visible externally.
|
||||
void GetFinalStateFromContainedDecoder();
|
||||
|
||||
// Processes a single dir entry of the icon resource
|
||||
void ProcessDirEntry(IconDirEntry& aTarget);
|
||||
// Sets the hotspot property of if we have a cursor
|
||||
void SetHotSpotIfCursor();
|
||||
// Creates a bitmap file header buffer, returns true if successful
|
||||
bool FillBitmapFileHeaderBuffer(int8_t* bfh);
|
||||
// Fixes the ICO height to match that of the BIH.
|
||||
@ -100,40 +73,36 @@ private:
|
||||
// Returns false if invalid information is contained within.
|
||||
bool FixBitmapWidth(int8_t* bih);
|
||||
// Extract bitmap info header size count from BMP information header
|
||||
int32_t ReadBIHSize(const char* aBIH);
|
||||
int32_t ExtractBIHSizeFromBitmap(int8_t* bih);
|
||||
// Extract bit count from BMP information header
|
||||
int32_t ReadBPP(const char* aBIH);
|
||||
int32_t ExtractBPPFromBitmap(int8_t* bih);
|
||||
// Calculates the row size in bytes for the AND mask table
|
||||
uint32_t CalcAlphaRowSize();
|
||||
// Obtains the number of colors from the BPP, mBPP must be filled in
|
||||
uint16_t GetNumColors();
|
||||
|
||||
LexerTransition<ICOState> ReadHeader(const char* aData);
|
||||
LexerTransition<ICOState> ReadDirEntry(const char* aData);
|
||||
LexerTransition<ICOState> SniffResource(const char* aData);
|
||||
LexerTransition<ICOState> ReadPNG(const char* aData, uint32_t aLen);
|
||||
LexerTransition<ICOState> ReadBIH(const char* aData);
|
||||
LexerTransition<ICOState> ReadBMP(const char* aData, uint32_t aLen);
|
||||
LexerTransition<ICOState> PrepareForMask();
|
||||
LexerTransition<ICOState> ReadMaskRow(const char* aData);
|
||||
LexerTransition<ICOState> FinishResource();
|
||||
gfx::IntSize mResolution; // The requested -moz-resolution for this icon.
|
||||
uint16_t mBPP; // Stores the images BPP
|
||||
uint32_t mPos; // Keeps track of the position we have decoded up until
|
||||
uint16_t mNumIcons; // Stores the number of icons in the ICO file
|
||||
uint16_t mCurrIcon; // Stores the current dir entry index we are processing
|
||||
uint32_t mImageOffset; // Stores the offset of the image data we want
|
||||
uint8_t* mRow; // Holds one raw line of the image
|
||||
int32_t mCurLine; // Line index of the image that's currently being decoded
|
||||
uint32_t mRowBytes; // How many bytes of the row were already received
|
||||
int32_t mOldLine; // Previous index of the line
|
||||
nsRefPtr<Decoder> mContainedDecoder; // Contains either a BMP or PNG resource
|
||||
|
||||
StreamingLexer<ICOState, 32> mLexer; // The lexer.
|
||||
Maybe<Downscaler> mDownscaler; // Our downscaler, if we're downscaling.
|
||||
nsRefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
|
||||
char mBIHraw[40]; // The bitmap information header.
|
||||
IconDirEntry mDirEntry; // The dir entry for the selected resource.
|
||||
IntSize mBiggestResourceSize; // Used to select the intrinsic size.
|
||||
IntSize mBiggestResourceHotSpot; // Used to select the intrinsic size.
|
||||
uint16_t mBiggestResourceColorDepth; // Used to select the intrinsic size.
|
||||
int32_t mBestResourceDelta; // Used to select the best resource.
|
||||
uint16_t mBestResourceColorDepth; // Used to select the best resource.
|
||||
uint16_t mNumIcons; // Stores the number of icons in the ICO file.
|
||||
uint16_t mCurrIcon; // Stores the current dir entry index we are processing.
|
||||
uint16_t mBPP; // The BPP of the resource we're decoding.
|
||||
uint32_t mMaskRowSize; // The size in bytes of each row in the BMP alpha mask.
|
||||
uint32_t mCurrMaskLine; // The line of the BMP alpha mask we're processing.
|
||||
bool mIsCursor; // Is this ICO a cursor?
|
||||
char mDirEntryArray[ICODIRENTRYSIZE]; // Holds the current dir entry buffer
|
||||
IconDirEntry mDirEntry; // Holds a decoded dir entry
|
||||
// Holds the potential bytes that can be a PNG signature
|
||||
char mSignature[PNGSIGNATURESIZE];
|
||||
// Holds the potential bytes for a bitmap information header
|
||||
char mBIHraw[40];
|
||||
// Stores whether or not the icon file we are processing has type 1 (icon)
|
||||
bool mIsCursor;
|
||||
// Stores whether or not the contained resource is a PNG
|
||||
bool mIsPNG;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
|
@ -119,7 +119,7 @@ native nsIntSizeByVal(nsIntSize);
|
||||
*
|
||||
* Internally, imgIContainer also manages animation of images.
|
||||
*/
|
||||
[scriptable, builtinclass, uuid(4e5a0547-6c54-4051-8b52-1f2fdd667696)]
|
||||
[scriptable, builtinclass, uuid(4880727a-5673-44f7-b248-f6c86e22a434)]
|
||||
interface imgIContainer : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -267,21 +267,6 @@ interface imgIContainer : nsISupports
|
||||
[noscript, notxpcom] TempRefSourceSurface getFrame(in uint32_t aWhichFrame,
|
||||
in uint32_t aFlags);
|
||||
|
||||
/**
|
||||
* Get a surface for the given frame at the specified size. Matching the
|
||||
* requested size is best effort; it's not guaranteed that the surface you get
|
||||
* will be a perfect match. (Some reasons you may get a surface of a different
|
||||
* size include: if you requested upscaling, if downscale-during-decode is
|
||||
* disabled, or if you didn't request the first frame.)
|
||||
*
|
||||
* @param aSize The desired size.
|
||||
* @param aWhichFrame Frame specifier of the FRAME_* variety.
|
||||
* @param aFlags Flags of the FLAG_* variety
|
||||
*/
|
||||
[noscript, notxpcom] TempRefSourceSurface getFrameAtSize([const] in nsIntSize aSize,
|
||||
in uint32_t aWhichFrame,
|
||||
in uint32_t aFlags);
|
||||
|
||||
/**
|
||||
* Whether this image is opaque (i.e., needs a background painted behind it).
|
||||
*/
|
||||
|
@ -198,27 +198,26 @@ imgTools::EncodeScaledImage(imgIContainer* aContainer,
|
||||
return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
|
||||
}
|
||||
|
||||
// Retrieve the image's size.
|
||||
int32_t imageWidth = 0;
|
||||
int32_t imageHeight = 0;
|
||||
aContainer->GetWidth(&imageWidth);
|
||||
aContainer->GetHeight(&imageHeight);
|
||||
// Use frame 0 from the image container.
|
||||
RefPtr<SourceSurface> frame =
|
||||
aContainer->GetFrame(imgIContainer::FRAME_FIRST,
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
|
||||
|
||||
int32_t frameWidth = frame->GetSize().width;
|
||||
int32_t frameHeight = frame->GetSize().height;
|
||||
|
||||
// If the given width or height is zero we'll replace it with the image's
|
||||
// original dimensions.
|
||||
IntSize scaledSize(aScaledWidth == 0 ? imageWidth : aScaledWidth,
|
||||
aScaledHeight == 0 ? imageHeight : aScaledHeight);
|
||||
|
||||
// Use frame 0 from the image container.
|
||||
RefPtr<SourceSurface> frame =
|
||||
aContainer->GetFrameAtSize(scaledSize,
|
||||
imgIContainer::FRAME_FIRST,
|
||||
imgIContainer::FLAG_HIGH_QUALITY_SCALING |
|
||||
imgIContainer::FLAG_SYNC_DECODE);
|
||||
NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
|
||||
if (aScaledWidth == 0) {
|
||||
aScaledWidth = frameWidth;
|
||||
} else if (aScaledHeight == 0) {
|
||||
aScaledHeight = frameHeight;
|
||||
}
|
||||
|
||||
RefPtr<DataSourceSurface> dataSurface =
|
||||
Factory::CreateDataSourceSurface(scaledSize, SurfaceFormat::B8G8R8A8);
|
||||
Factory::CreateDataSourceSurface(IntSize(aScaledWidth, aScaledHeight),
|
||||
SurfaceFormat::B8G8R8A8);
|
||||
if (NS_WARN_IF(!dataSurface)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -239,10 +238,9 @@ imgTools::EncodeScaledImage(imgIContainer* aContainer,
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
IntSize frameSize = frame->GetSize();
|
||||
dt->DrawSurface(frame,
|
||||
Rect(0, 0, scaledSize.width, scaledSize.height),
|
||||
Rect(0, 0, frameSize.width, frameSize.height),
|
||||
Rect(0, 0, aScaledWidth, aScaledHeight),
|
||||
Rect(0, 0, frameWidth, frameHeight),
|
||||
DrawSurfaceOptions(),
|
||||
DrawOptions(1.0f, CompositionOp::OP_SOURCE));
|
||||
|
||||
|
@ -52,8 +52,3 @@ skip-if(AddressSanitizer) skip-if(B2G) load 944353.jpg
|
||||
load invalid-disposal-method-1.gif
|
||||
load invalid-disposal-method-2.gif
|
||||
load invalid-disposal-method-3.gif
|
||||
|
||||
# Ensure we handle ICO directory entries which specify the wrong size for the
|
||||
# contained resource.
|
||||
load invalid_ico_height.ico
|
||||
load invalid_ico_width.ico
|
||||
|
@ -212,7 +212,8 @@ TEST(ImageDecoders, ICOSingleChunk)
|
||||
CheckDecoderSingleChunk(GreenICOTestCase());
|
||||
}
|
||||
|
||||
TEST(ImageDecoders, ICOMultiChunk)
|
||||
// XXX(seth): Disabled. We'll fix this in bug 1196066.
|
||||
TEST(ImageDecoders, DISABLED_ICOMultiChunk)
|
||||
{
|
||||
CheckDecoderMultiChunk(GreenICOTestCase());
|
||||
}
|
||||
|
@ -1,266 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
#include "mozilla/Vector.h"
|
||||
#include "StreamingLexer.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
||||
enum class TestState
|
||||
{
|
||||
ONE,
|
||||
TWO,
|
||||
THREE,
|
||||
UNBUFFERED,
|
||||
SUCCESS,
|
||||
FAILURE
|
||||
};
|
||||
|
||||
void
|
||||
CheckData(const char* aData, size_t aLength)
|
||||
{
|
||||
EXPECT_TRUE(aLength == 3);
|
||||
EXPECT_EQ(1, aData[0]);
|
||||
EXPECT_EQ(2, aData[1]);
|
||||
EXPECT_EQ(3, aData[2]);
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLex(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckData(aData, aLength);
|
||||
return Transition::To(TestState::TWO, 3);
|
||||
case TestState::TWO:
|
||||
CheckData(aData, aLength);
|
||||
return Transition::To(TestState::THREE, 3);
|
||||
case TestState::THREE:
|
||||
CheckData(aData, aLength);
|
||||
return Transition::Terminate(TestState::SUCCESS);
|
||||
default:
|
||||
EXPECT_TRUE(false); // Shouldn't get here.
|
||||
return Transition::Terminate(TestState::FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithUnbuffered(TestState aState, const char* aData, size_t aLength,
|
||||
Vector<char>& aUnbufferedVector)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckData(aData, aLength);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
|
||||
case TestState::UNBUFFERED:
|
||||
EXPECT_TRUE(aLength <= 3);
|
||||
aUnbufferedVector.append(aData, aLength);
|
||||
return Transition::ContinueUnbuffered(TestState::UNBUFFERED);
|
||||
case TestState::TWO:
|
||||
CheckData(aUnbufferedVector.begin(), aUnbufferedVector.length());
|
||||
return Transition::To(TestState::THREE, 3);
|
||||
case TestState::THREE:
|
||||
CheckData(aData, aLength);
|
||||
return Transition::Terminate(TestState::SUCCESS);
|
||||
default:
|
||||
EXPECT_TRUE(false);
|
||||
return Transition::Terminate(TestState::FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
LexerTransition<TestState>
|
||||
DoLexWithUnbufferedTerminate(TestState aState, const char* aData, size_t aLength)
|
||||
{
|
||||
switch (aState) {
|
||||
case TestState::ONE:
|
||||
CheckData(aData, aLength);
|
||||
return Transition::ToUnbuffered(TestState::TWO, TestState::UNBUFFERED, 3);
|
||||
case TestState::UNBUFFERED:
|
||||
return Transition::Terminate(TestState::SUCCESS);
|
||||
default:
|
||||
EXPECT_TRUE(false);
|
||||
return Transition::Terminate(TestState::FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ImageStreamingLexer, SingleChunk)
|
||||
{
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
|
||||
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
|
||||
|
||||
// Test delivering all the data at once.
|
||||
Maybe<TestState> result = lexer.Lex(data, sizeof(data), DoLex);
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::SUCCESS, *result);
|
||||
}
|
||||
|
||||
TEST(ImageStreamingLexer, SingleChunkWithUnbuffered)
|
||||
{
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
|
||||
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
|
||||
Vector<char> unbufferedVector;
|
||||
|
||||
// Test delivering all the data at once.
|
||||
Maybe<TestState> result =
|
||||
lexer.Lex(data, sizeof(data),
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
|
||||
});
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::SUCCESS, *result);
|
||||
}
|
||||
|
||||
TEST(ImageStreamingLexer, ChunkPerState)
|
||||
{
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
|
||||
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
|
||||
|
||||
// Test delivering in perfectly-sized chunks, one per state.
|
||||
for (unsigned i = 0 ; i < 3 ; ++i) {
|
||||
Maybe<TestState> result = lexer.Lex(data + 3 * i, 3, DoLex);
|
||||
|
||||
if (i == 2) {
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::SUCCESS, *result);
|
||||
} else {
|
||||
EXPECT_TRUE(result.isNothing());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ImageStreamingLexer, ChunkPerStateWithUnbuffered)
|
||||
{
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
|
||||
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
|
||||
Vector<char> unbufferedVector;
|
||||
|
||||
// Test delivering in perfectly-sized chunks, one per state.
|
||||
for (unsigned i = 0 ; i < 3 ; ++i) {
|
||||
Maybe<TestState> result =
|
||||
lexer.Lex(data + 3 * i, 3,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
|
||||
});
|
||||
|
||||
if (i == 2) {
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::SUCCESS, *result);
|
||||
} else {
|
||||
EXPECT_TRUE(result.isNothing());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ImageStreamingLexer, OneByteChunks)
|
||||
{
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
|
||||
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
|
||||
|
||||
// Test delivering in one byte chunks.
|
||||
for (unsigned i = 0 ; i < 9 ; ++i) {
|
||||
Maybe<TestState> result = lexer.Lex(data + i, 1, DoLex);
|
||||
|
||||
if (i == 8) {
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::SUCCESS, *result);
|
||||
} else {
|
||||
EXPECT_TRUE(result.isNothing());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ImageStreamingLexer, OneByteChunksWithUnbuffered)
|
||||
{
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
|
||||
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
|
||||
Vector<char> unbufferedVector;
|
||||
|
||||
// Test delivering in one byte chunks.
|
||||
for (unsigned i = 0 ; i < 9 ; ++i) {
|
||||
Maybe<TestState> result =
|
||||
lexer.Lex(data + i, 1,
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
return DoLexWithUnbuffered(aState, aData, aLength, unbufferedVector);
|
||||
});
|
||||
|
||||
if (i == 8) {
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::SUCCESS, *result);
|
||||
} else {
|
||||
EXPECT_TRUE(result.isNothing());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ImageStreamingLexer, TerminateSuccess)
|
||||
{
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
|
||||
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
|
||||
|
||||
// Test that Terminate is "sticky".
|
||||
Maybe<TestState> result =
|
||||
lexer.Lex(data, sizeof(data),
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(aState == TestState::ONE);
|
||||
return Transition::Terminate(TestState::SUCCESS);
|
||||
});
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::SUCCESS, *result);
|
||||
|
||||
result =
|
||||
lexer.Lex(data, sizeof(data),
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(false); // Shouldn't get here.
|
||||
return Transition::Terminate(TestState::FAILURE);
|
||||
});
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::SUCCESS, *result);
|
||||
}
|
||||
|
||||
TEST(ImageStreamingLexer, TerminateFailure)
|
||||
{
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
|
||||
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
|
||||
|
||||
// Test that Terminate is "sticky".
|
||||
Maybe<TestState> result =
|
||||
lexer.Lex(data, sizeof(data),
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(aState == TestState::ONE);
|
||||
return Transition::Terminate(TestState::FAILURE);
|
||||
});
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::FAILURE, *result);
|
||||
|
||||
result =
|
||||
lexer.Lex(data, sizeof(data),
|
||||
[&](TestState aState, const char* aData, size_t aLength) {
|
||||
EXPECT_TRUE(false); // Shouldn't get here.
|
||||
return Transition::Terminate(TestState::FAILURE);
|
||||
});
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::FAILURE, *result);
|
||||
}
|
||||
|
||||
TEST(ImageStreamingLexer, TerminateUnbuffered)
|
||||
{
|
||||
StreamingLexer<TestState> lexer(Transition::To(TestState::ONE, 3));
|
||||
char data[9] = { 1, 2, 3, 1, 2, 3, 1, 2, 3 };
|
||||
|
||||
// Test that Terminate works during an unbuffered read.
|
||||
for (unsigned i = 0 ; i < 9 ; ++i) {
|
||||
Maybe<TestState> result =
|
||||
lexer.Lex(data + i, 1, DoLexWithUnbufferedTerminate);
|
||||
|
||||
if (i > 2) {
|
||||
EXPECT_TRUE(result.isSome());
|
||||
EXPECT_EQ(TestState::SUCCESS, *result);
|
||||
} else {
|
||||
EXPECT_TRUE(result.isNothing());
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,6 @@ UNIFIED_SOURCES = [
|
||||
'TestDecoders.cpp',
|
||||
'TestDecodeToSurface.cpp',
|
||||
'TestMetadata.cpp',
|
||||
'TestStreamingLexer.cpp',
|
||||
]
|
||||
|
||||
TEST_HARNESS_FILES.gtest += [
|
||||
|
@ -57,9 +57,8 @@ function testFiles() {
|
||||
yield ["opaque.bmp", false];
|
||||
|
||||
// ICO files which contain BMPs have an additional type of transparency - the
|
||||
// AND mask - that warrants separate testing. (Although, after bug 1201796,
|
||||
// all ICOs are considered transparent.)
|
||||
yield ["ico-bmp-opaque.ico", true];
|
||||
// AND mask - that warrants separate testing.
|
||||
yield ["ico-bmp-opaque.ico", false];
|
||||
yield ["ico-bmp-transparent.ico", true];
|
||||
|
||||
// SVGs are always transparent.
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 894 B After Width: | Height: | Size: 894 B |
Before Width: | Height: | Size: 894 B After Width: | Height: | Size: 894 B |
@ -8,3 +8,8 @@
|
||||
== wrapper.html?invalid-compression-RLE8.ico about:blank
|
||||
# Invalid compression value - detected when decoding the image data.
|
||||
== wrapper.html?invalid-compression.ico about:blank
|
||||
|
||||
# Invalid ICO width and heigth should be ignored if the
|
||||
# contained BMP is correct.
|
||||
== invalid_ico_height.ico 16x16.png
|
||||
== invalid_ico_width.ico 16x16.png
|
||||
|
@ -1,3 +1,14 @@
|
||||
# ICO BMP and PNG mixed tests
|
||||
|
||||
== mixed-bmp-png.ico mixed-bmp-png48.png
|
||||
== mixed-bmp-png.ico mixed-bmp-png.png
|
||||
|
||||
# Using media fragments to select different resolutions
|
||||
|
||||
== mixed-bmp-png.ico#-moz-resolution=8,8 mixed-bmp-png.png
|
||||
== mixed-bmp-png.ico#test=true&-moz-resolution=8,8&other mixed-bmp-png.png
|
||||
== mixed-bmp-png.ico#-moz-resolution=32,32 mixed-bmp-png32.png
|
||||
== mixed-bmp-png.ico#-moz-resolution=39,39 mixed-bmp-png48.png
|
||||
== mixed-bmp-png.ico#-moz-resolution=40,40 mixed-bmp-png48.png
|
||||
== mixed-bmp-png.ico#-moz-resolution=48,48 mixed-bmp-png48.png
|
||||
== mixed-bmp-png.ico#-moz-resolution=64,64 mixed-bmp-png48.png
|
||||
== mixed-bmp-png.ico#-moz-resolution=64 mixed-bmp-png.png # Bad syntax will fall back to lowest resolution
|
||||
|
Before Width: | Height: | Size: 92 B After Width: | Height: | Size: 92 B |
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 950 B After Width: | Height: | Size: 948 B |
@ -169,7 +169,7 @@ var encodedBytes = streamToArray(istream);
|
||||
var refName = "image1png16x16.jpg";
|
||||
var refFile = do_get_file(refName);
|
||||
istream = getFileInputStream(refFile);
|
||||
do_check_eq(istream.available(), 1051);
|
||||
do_check_eq(istream.available(), 1078);
|
||||
var referenceBytes = streamToArray(istream);
|
||||
|
||||
// compare the encoder's output to the reference file.
|
||||
@ -228,7 +228,7 @@ encodedBytes = streamToArray(istream);
|
||||
refName = isWindows ? "image2jpg16x16-win.png" : "image2jpg16x16.png";
|
||||
refFile = do_get_file(refName);
|
||||
istream = getFileInputStream(refFile);
|
||||
do_check_eq(istream.available(), 950);
|
||||
do_check_eq(istream.available(), 948);
|
||||
referenceBytes = streamToArray(istream);
|
||||
|
||||
// compare the encoder's output to the reference file.
|
||||
@ -691,10 +691,8 @@ var errsrc = "none";
|
||||
try {
|
||||
container = imgTools.decodeImage(istream, inMimeType);
|
||||
|
||||
// We expect to hit an error during encoding because the ICO header of the
|
||||
// image is fine, but the actual resources are corrupt. Since decodeImage()
|
||||
// only performs a metadata decode, it doesn't decode far enough to realize
|
||||
// this, but we'll find out when we do a full decode during encodeImage().
|
||||
// We should never hit this - decodeImage throws an assertion because the
|
||||
// image decoded doesn't have enough frames.
|
||||
try {
|
||||
istream = imgTools.encodeImage(container, "image/png");
|
||||
} catch (e) {
|
||||
@ -706,7 +704,7 @@ try {
|
||||
errsrc = "decode";
|
||||
}
|
||||
|
||||
do_check_eq(errsrc, "encode");
|
||||
do_check_eq(errsrc, "decode");
|
||||
checkExpectedError(/NS_ERROR_FAILURE/, err);
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 950 B After Width: | Height: | Size: 948 B |
Before Width: | Height: | Size: 979 B After Width: | Height: | Size: 868 B |