Skip to content

Commit

Permalink
#501: convert to Demux API; add colour profile support
Browse files Browse the repository at this point in the history
  • Loading branch information
classilla committed Feb 9, 2019
1 parent c2ce1fb commit ceed596
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 61 deletions.
178 changes: 140 additions & 38 deletions image/decoders/nsWEBPDecoder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@
/* 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/. */
/* Contributor(s):
* Vikas Arora <vikasa@google.com> */

/* WebP for TenFourFox. Actually works on big endian.
Incorporates original version from bug 600919 with later updates.
See also bug 1294490. */

#include "nsWEBPDecoder.h"

#include "ImageLogging.h"
#include "gfxColor.h"
#include "gfxPlatform.h"
#include "webp/demux.h"

namespace mozilla {
namespace image {
Expand All @@ -29,6 +32,8 @@ nsWEBPDecoder::nsWEBPDecoder(RasterImage* aImage)
, mWidth(0)
, mHeight(0)
, haveSize(false)
, mProfile(nullptr)
, mTransform(nullptr)
{
MOZ_LOG(gWEBPDecoderAccountingLog, LogLevel::Debug,
("nsWEBPDecoder::nsWEBPDecoder: Creating WEBP decoder %p",
Expand Down Expand Up @@ -56,7 +61,9 @@ nsWEBPDecoder::InitInternal()

MOZ_ASSERT(!mImageData, "Shouldn't have a buffer yet");

#ifdef __ppc__
// Premultiplied alpha required. We may change the colourspace
// if colour management is required (see below).
#if MOZ_BIG_ENDIAN
mDecBuf.colorspace = MODE_Argb;
#else
mDecBuf.colorspace = MODE_bgrA;
Expand All @@ -74,72 +81,134 @@ void
nsWEBPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
{
const uint8_t* buf = (const uint8_t*)aBuffer;
uint32_t flags = 0;

if (!haveSize) {
WebPDemuxer *demux = nullptr;
WebPDemuxState state;
WebPData fragment;

if (!aCount) return;

fragment.bytes = buf;
fragment.size = aCount;
demux = WebPDemuxPartial(&fragment, &state);
if (!demux) {
// We don't even have enough data to determine if we have headers.
return;
}
if (state == WEBP_DEMUX_PARSE_ERROR) {
// Hmm.
NS_WARNING("Parse error on WebP image");
WebPDemuxDelete(demux);
return;
}
if (state == WEBP_DEMUX_PARSING_HEADER) {
// Not enough data yet.
WebPDemuxDelete(demux);
return;
}

if (IsMetadataDecode() || !haveSize) {
WebPBitstreamFeatures features;
const VP8StatusCode rv = WebPGetFeatures(buf, aCount, &features);
flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS);

if (rv == VP8_STATUS_OK) {
if (features.has_animation) {
PostDecoderError(NS_ERROR_FAILURE);
// Make sure chunks are available, if we need them.
if (flags & ICCP_FLAG) {
// Embedded colour profile chunk.
if (state != WEBP_DEMUX_DONE) {
// Not enough data yet.
NS_WARNING("Waiting for WebP chunks to load");
WebPDemuxDelete(demux);
return;
}
if (features.has_alpha) {
PostHasTransparency();
}
// Post our size to the superclass
if (IsMetadataDecode()) {
PostSize(features.width, features.height);
}
mWidth = features.width;
mHeight = features.height;
mDecBuf.width = mWidth;
mDecBuf.height = mHeight;
mDecBuf.u.RGBA.stride = mWidth * sizeof(uint32_t);
mDecBuf.u.RGBA.size = mDecBuf.u.RGBA.stride * mHeight;
haveSize = true;
} else if (rv != VP8_STATUS_NOT_ENOUGH_DATA) {
PostDecoderError(NS_ERROR_FAILURE);
return;
} else {
}

// Valid demuxer available.
if (flags & ANIMATION_FLAG) {
NS_WARNING("animated WebP not yet supported"); // XXX
}
if (flags & ALPHA_FLAG) {
PostHasTransparency();
}

// Handle embedded colour profiles.
if (!mProfile && (flags & ICCP_FLAG)) {
if (gfxPlatform::GetCMSOutputProfile()) {
WebPChunkIterator chunk_iter;

if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter)) {
#if DEBUG
fprintf(stderr, "WebP had unexpected return code: %d\n", rv);
fprintf(stderr, "WebP has embedded colour profile (%d bytes).\n", chunk_iter.chunk.size);
#endif
return;
mProfile = qcms_profile_from_memory(
reinterpret_cast<const char*>(chunk_iter.chunk.bytes),
chunk_iter.chunk.size);
if (mProfile) {
int intent = gfxPlatform::GetRenderingIntent();
if (intent == -1)
intent = qcms_profile_get_rendering_intent(mProfile);
mTransform = qcms_transform_create(mProfile,
QCMS_DATA_RGBA_8,
gfxPlatform::GetCMSOutputProfile(),
QCMS_DATA_RGBA_8,
(qcms_intent)intent);
mDecBuf.colorspace = MODE_rgbA; // byte-swapped later
}
WebPDemuxReleaseChunkIterator(&chunk_iter);
}
}
}

// If we're doing a size decode, we're done.
if (IsMetadataDecode()) return;
mWidth = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);
mHeight = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);
// Post our size to the superclass
PostSize(mWidth, mHeight);
mDecBuf.width = mWidth;
mDecBuf.height = mHeight;
mDecBuf.u.RGBA.stride = mWidth * sizeof(uint32_t);
mDecBuf.u.RGBA.size = mDecBuf.u.RGBA.stride * mHeight;
haveSize = true;

WebPDemuxDelete(demux);
}

MOZ_ASSERT(!mImageData, "Already have a buffer allocated?");
MOZ_ASSERT(haveSize, "Didn't fetch metadata?");
PostSize(mWidth, mHeight);
nsresult rv_ = AllocateBasicFrame();
if (NS_FAILED(rv_)) {
return;
if (IsMetadataDecode()) {
// Nothing else to do.
return;
}

if (!mImageData) {
MOZ_ASSERT(haveSize, "Didn't fetch metadata?");
nsresult rv_ = AllocateBasicFrame();
if (NS_FAILED(rv_)) {
return;
}
}
MOZ_ASSERT(mImageData, "Should have a buffer now");
MOZ_ASSERT(mDecoder, "Should have a decoder now");
mDecBuf.u.RGBA.rgba = mImageData; // no longer null
const VP8StatusCode rv = WebPIAppend(mDecoder, buf, aCount);

if (rv == VP8_STATUS_OUT_OF_MEMORY) {
NS_WARNING("WebP out of memory");
PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
return;
} else if (rv == VP8_STATUS_INVALID_PARAM ||
rv == VP8_STATUS_BITSTREAM_ERROR) {
NS_WARNING("WebP bitstream error");
PostDataError();
return;
} else if (rv == VP8_STATUS_UNSUPPORTED_FEATURE ||
rv == VP8_STATUS_USER_ABORT) {
NS_WARNING("WebP unsupported feature");
PostDecoderError(NS_ERROR_FAILURE);
return;
}

// Catch any remaining erroneous return value.
if (rv != VP8_STATUS_OK && rv != VP8_STATUS_SUSPENDED) {
#if DEBUG
fprintf(stderr, "WebP unexpected parsing error %d\n", rv);
#endif
PostDecoderError(NS_ERROR_FAILURE);
return;
}
Expand All @@ -149,7 +218,7 @@ nsWEBPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
int height = 0;
int stride = 0;

const uint8_t* data =
uint8_t* data =
WebPIDecGetRGB(mDecoder, &lastLineRead, &width, &height, &stride);

// WebP encoded image data hasn't been read yet, return.
Expand All @@ -164,11 +233,35 @@ nsWEBPDecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
}

if (!mImageData) {
NS_WARNING("WebP y u no haz image");
PostDecoderError(NS_ERROR_FAILURE);
return;
}

if (lastLineRead > mLastLine) {
if (mTransform) {
for (int row = mLastLine; row < lastLineRead; row++) {
MOZ_ASSERT(!(stride % 4)); // should be RGBA alignment
uint8_t* src = data + row * stride;
qcms_transform_data(mTransform, src, src, width);
for (uint8_t* i = src; i < src + stride; i+=4) {
#if MOZ_BIG_ENDIAN
// RGBA -> ARGB
uint8_t a = i[3];
i[3] = i[2];
i[2] = i[1];
i[1] = i[0];
i[0] = a;
#else
// RGBA -> BGRA
uint8_t r = i[2];
i[2] = i[0];
i[0] = r;
#endif
}
}
}

// Invalidate
nsIntRect r(0, mLastLine, width, lastLineRead - mLastLine);
PostInvalidation(r);
Expand All @@ -192,6 +285,15 @@ nsWEBPDecoder::FinishInternal()
PostDecodeDone();

mDecoder = nullptr;

if (mProfile) {
if (mTransform) {
qcms_transform_release(mTransform);
mTransform = nullptr;
}
qcms_profile_release(mProfile);
mProfile = nullptr;
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions image/decoders/nsWEBPDecoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ class nsWEBPDecoder : public Decoder
int mWidth; // Image Width
int mHeight; // Image Height
bool haveSize; // True if mDecBuf contains image dimension

qcms_profile* mProfile; // embedded ICC profile
qcms_transform* mTransform; // resulting qcms transform
};

} // namespace image
Expand Down
29 changes: 6 additions & 23 deletions media/libwebp/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
# 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/.


src_dir = 'src/'
moz_dir = src_dir + 'moz/'
dec_dir = src_dir + 'dec/'
dsp_dir = src_dir + 'dsp/'
utils_dir = src_dir + 'utils/'
webp_dir = src_dir + 'webp/'
demux_dir = src_dir + 'demux/'

EXPORTS.webp += [
webp_dir + 'decode.h',
Expand All @@ -32,6 +32,10 @@ dec_sources = [
dec_dir + 'webp_dec.c',
]

demux_sources = [
demux_dir + 'demux.c',
]

dsp_sources = [
dsp_dir + 'alpha_processing.c',
dsp_dir + 'alpha_processing_sse2.c',
Expand Down Expand Up @@ -68,31 +72,10 @@ utils_sources = [
]

# NB: dsp especially isn't UNIFIED_SOURCES friendly.
SOURCES += dec_sources + dsp_sources + moz_sources + utils_sources

if CONFIG['OS_TARGET'] == 'Android':
# Older versions of the Android NDK don't pre-define anything to indicate
# the OS they're on, so do it for them.
DEFINES['__linux__'] = True
CFLAGS += [
'-I%s/sources/android/cpufeatures/' % CONFIG['ANDROID_NDK'],
]

if not CONFIG['MOZ_WEBRTC']:
SOURCES += [
'%s/sources/android/cpufeatures/cpu-features.c' % CONFIG['ANDROID_NDK'],
]

# TODO: aarch64?
if CONFIG['CPU_ARCH'] == 'arm' and CONFIG['GNU_CC']:
for f in dsp_sources:
if f.endswith('.c') and 'neon' in f:
SOURCES[f].flags += ['-mfloat-abi=softfp', '-mfpu=neon']
SOURCES += dec_sources + demux_sources + dsp_sources + moz_sources + utils_sources

Library('mozwebp')

#MSVC_ENABLE_PGO = True

if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
NO_VISIBILITY_FLAGS = True

Expand Down

0 comments on commit ceed596

Please sign in to comment.