mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-02 21:37:32 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
15
image/encoders/bmp/moz.build
Normal file
15
image/encoders/bmp/moz.build
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'nsBMPEncoder.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/image',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
764
image/encoders/bmp/nsBMPEncoder.cpp
Normal file
764
image/encoders/bmp/nsBMPEncoder.cpp
Normal file
|
|
@ -0,0 +1,764 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#include "nsCRT.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
#include "mozilla/UniquePtrExtensions.h"
|
||||
#include "nsBMPEncoder.h"
|
||||
#include "prprf.h"
|
||||
#include "nsString.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsTArray.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
using namespace mozilla::image::bmp;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsBMPEncoder, imgIEncoder, nsIInputStream,
|
||||
nsIAsyncInputStream)
|
||||
|
||||
nsBMPEncoder::nsBMPEncoder() : mImageBufferStart(nullptr),
|
||||
mImageBufferCurr(0),
|
||||
mImageBufferSize(0),
|
||||
mImageBufferReadPoint(0),
|
||||
mFinished(false),
|
||||
mCallback(nullptr),
|
||||
mCallbackTarget(nullptr),
|
||||
mNotifyThreshold(0)
|
||||
{
|
||||
}
|
||||
|
||||
nsBMPEncoder::~nsBMPEncoder()
|
||||
{
|
||||
if (mImageBufferStart) {
|
||||
free(mImageBufferStart);
|
||||
mImageBufferStart = nullptr;
|
||||
mImageBufferCurr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// nsBMPEncoder::InitFromData
|
||||
//
|
||||
// One output option is supported: bpp=<bpp_value>
|
||||
// bpp specifies the bits per pixel to use where bpp_value can be 24 or 32
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::InitFromData(const uint8_t* aData,
|
||||
uint32_t aLength, // (unused, req'd by JS)
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aStride,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
// validate input format
|
||||
if (aInputFormat != INPUT_FORMAT_RGB &&
|
||||
aInputFormat != INPUT_FORMAT_RGBA &&
|
||||
aInputFormat != INPUT_FORMAT_HOSTARGB) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
CheckedInt32 check = CheckedInt32(aWidth) * 4;
|
||||
if (MOZ_UNLIKELY(!check.isValid())) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Stride is the padded width of each row, so it better be longer
|
||||
if ((aInputFormat == INPUT_FORMAT_RGB &&
|
||||
aStride < aWidth * 3) ||
|
||||
((aInputFormat == INPUT_FORMAT_RGBA ||
|
||||
aInputFormat == INPUT_FORMAT_HOSTARGB) &&
|
||||
aStride < aWidth * 4)) {
|
||||
NS_WARNING("Invalid stride for InitFromData");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride,
|
||||
aInputFormat, aOutputOptions);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = EndImageEncode();
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Just a helper method to make it explicit in calculations that we are dealing
|
||||
// with bytes and not bits
|
||||
static inline uint16_t
|
||||
BytesPerPixel(uint16_t aBPP)
|
||||
{
|
||||
return aBPP / 8;
|
||||
}
|
||||
|
||||
// Calculates the number of padding bytes that are needed per row of image data
|
||||
static inline uint32_t
|
||||
PaddingBytes(uint16_t aBPP, uint32_t aWidth)
|
||||
{
|
||||
uint32_t rowSize = aWidth * BytesPerPixel(aBPP);
|
||||
uint8_t paddingSize = 0;
|
||||
if (rowSize % 4) {
|
||||
paddingSize = (4 - (rowSize % 4));
|
||||
}
|
||||
return paddingSize;
|
||||
}
|
||||
|
||||
// See ::InitFromData for other info.
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::StartImageEncode(uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
// can't initialize more than once
|
||||
if (mImageBufferStart || mImageBufferCurr) {
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
// validate input format
|
||||
if (aInputFormat != INPUT_FORMAT_RGB &&
|
||||
aInputFormat != INPUT_FORMAT_RGBA &&
|
||||
aInputFormat != INPUT_FORMAT_HOSTARGB) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// parse and check any provided output options
|
||||
Version version;
|
||||
uint16_t bpp;
|
||||
nsresult rv = ParseOptions(aOutputOptions, version, bpp);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
MOZ_ASSERT(bpp <= 32);
|
||||
|
||||
rv = InitFileHeader(version, bpp, aWidth, aHeight);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
rv = InitInfoHeader(version, bpp, aWidth, aHeight);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
mImageBufferSize = mBMPFileHeader.filesize;
|
||||
mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize));
|
||||
if (!mImageBufferStart) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
mImageBufferCurr = mImageBufferStart;
|
||||
|
||||
EncodeFileHeader();
|
||||
EncodeInfoHeader();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Returns the number of bytes in the image buffer used.
|
||||
// For a BMP file, this is all bytes in the buffer.
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::GetImageBufferUsed(uint32_t* aOutputSize)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aOutputSize);
|
||||
*aOutputSize = mImageBufferSize;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Returns a pointer to the start of the image buffer
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::GetImageBuffer(char** aOutputBuffer)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aOutputBuffer);
|
||||
*aOutputBuffer = reinterpret_cast<char*>(mImageBufferStart);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::AddImageFrame(const uint8_t* aData,
|
||||
uint32_t aLength, // (unused, req'd by JS)
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aStride,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aFrameOptions)
|
||||
{
|
||||
// must be initialized
|
||||
if (!mImageBufferStart || !mImageBufferCurr) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// validate input format
|
||||
if (aInputFormat != INPUT_FORMAT_RGB &&
|
||||
aInputFormat != INPUT_FORMAT_RGBA &&
|
||||
aInputFormat != INPUT_FORMAT_HOSTARGB) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (mBMPInfoHeader.width < 0) {
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
CheckedUint32 size =
|
||||
CheckedUint32(mBMPInfoHeader.width) * CheckedUint32(BytesPerPixel(mBMPInfoHeader.bpp));
|
||||
if (MOZ_UNLIKELY(!size.isValid())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
auto row = MakeUniqueFallible<uint8_t[]>(size.value());
|
||||
if (!row) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
CheckedUint32 check = CheckedUint32(mBMPInfoHeader.height) * aStride;
|
||||
if (MOZ_UNLIKELY(!check.isValid())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// write each row: if we add more input formats, we may want to
|
||||
// generalize the conversions
|
||||
if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
|
||||
// BMP requires RGBA with post-multiplied alpha, so we need to convert
|
||||
for (int32_t y = mBMPInfoHeader.height - 1; y >= 0 ; y --) {
|
||||
ConvertHostARGBRow(&aData[y * aStride], row, mBMPInfoHeader.width);
|
||||
if(mBMPInfoHeader.bpp == 24) {
|
||||
EncodeImageDataRow24(row.get());
|
||||
} else {
|
||||
EncodeImageDataRow32(row.get());
|
||||
}
|
||||
}
|
||||
} else if (aInputFormat == INPUT_FORMAT_RGBA) {
|
||||
// simple RGBA, no conversion needed
|
||||
for (int32_t y = 0; y < mBMPInfoHeader.height; y++) {
|
||||
if (mBMPInfoHeader.bpp == 24) {
|
||||
EncodeImageDataRow24(row.get());
|
||||
} else {
|
||||
EncodeImageDataRow32(row.get());
|
||||
}
|
||||
}
|
||||
} else if (aInputFormat == INPUT_FORMAT_RGB) {
|
||||
// simple RGB, no conversion needed
|
||||
for (int32_t y = 0; y < mBMPInfoHeader.height; y++) {
|
||||
if (mBMPInfoHeader.bpp == 24) {
|
||||
EncodeImageDataRow24(&aData[y * aStride]);
|
||||
} else {
|
||||
EncodeImageDataRow32(&aData[y * aStride]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
NS_NOTREACHED("Bad format type");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::EndImageEncode()
|
||||
{
|
||||
// must be initialized
|
||||
if (!mImageBufferStart || !mImageBufferCurr) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
mFinished = true;
|
||||
NotifyListener();
|
||||
|
||||
// if output callback can't get enough memory, it will free our buffer
|
||||
if (!mImageBufferStart || !mImageBufferCurr) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
// Parses the encoder options and sets the bits per pixel to use
|
||||
// See InitFromData for a description of the parse options
|
||||
nsresult
|
||||
nsBMPEncoder::ParseOptions(const nsAString& aOptions, Version& aVersionOut,
|
||||
uint16_t& aBppOut)
|
||||
{
|
||||
aVersionOut = VERSION_3;
|
||||
aBppOut = 24;
|
||||
|
||||
// Parse the input string into a set of name/value pairs.
|
||||
// From a format like: name=value;bpp=<bpp_value>;name=value
|
||||
// to format: [0] = name=value, [1] = bpp=<bpp_value>, [2] = name=value
|
||||
nsTArray<nsCString> nameValuePairs;
|
||||
if (!ParseString(NS_ConvertUTF16toUTF8(aOptions), ';', nameValuePairs)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// For each name/value pair in the set
|
||||
for (uint32_t i = 0; i < nameValuePairs.Length(); ++i) {
|
||||
|
||||
// Split the name value pair [0] = name, [1] = value
|
||||
nsTArray<nsCString> nameValuePair;
|
||||
if (!ParseString(nameValuePairs[i], '=', nameValuePair)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (nameValuePair.Length() != 2) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Parse the bpp portion of the string name=value;version=<version_value>;
|
||||
// name=value
|
||||
if (nameValuePair[0].Equals("version",
|
||||
nsCaseInsensitiveCStringComparator())) {
|
||||
if (nameValuePair[1].EqualsLiteral("3")) {
|
||||
aVersionOut = VERSION_3;
|
||||
} else if (nameValuePair[1].EqualsLiteral("5")) {
|
||||
aVersionOut = VERSION_5;
|
||||
} else {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the bpp portion of the string name=value;bpp=<bpp_value>;name=value
|
||||
if (nameValuePair[0].Equals("bpp", nsCaseInsensitiveCStringComparator())) {
|
||||
if (nameValuePair[1].EqualsLiteral("24")) {
|
||||
aBppOut = 24;
|
||||
} else if (nameValuePair[1].EqualsLiteral("32")) {
|
||||
aBppOut = 32;
|
||||
} else {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::Close()
|
||||
{
|
||||
if (mImageBufferStart) {
|
||||
free(mImageBufferStart);
|
||||
mImageBufferStart = nullptr;
|
||||
mImageBufferSize = 0;
|
||||
mImageBufferReadPoint = 0;
|
||||
mImageBufferCurr = nullptr;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Obtains the available bytes to read
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::Available(uint64_t* _retval)
|
||||
{
|
||||
if (!mImageBufferStart || !mImageBufferCurr) {
|
||||
return NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
*_retval = GetCurrentImageBufferOffset() - mImageBufferReadPoint;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// [noscript] Reads bytes which are available
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
|
||||
{
|
||||
return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
|
||||
}
|
||||
|
||||
// [noscript] Reads segments
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
|
||||
uint32_t aCount, uint32_t* _retval)
|
||||
{
|
||||
uint32_t maxCount = GetCurrentImageBufferOffset() - mImageBufferReadPoint;
|
||||
if (maxCount == 0) {
|
||||
*_retval = 0;
|
||||
return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
|
||||
}
|
||||
|
||||
if (aCount > maxCount) {
|
||||
aCount = maxCount;
|
||||
}
|
||||
nsresult rv = aWriter(this, aClosure,
|
||||
reinterpret_cast<const char*>(mImageBufferStart +
|
||||
mImageBufferReadPoint),
|
||||
0, aCount, _retval);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
NS_ASSERTION(*_retval <= aCount, "bad write count");
|
||||
mImageBufferReadPoint += *_retval;
|
||||
}
|
||||
// errors returned from the writer end here!
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::IsNonBlocking(bool* _retval)
|
||||
{
|
||||
*_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::AsyncWait(nsIInputStreamCallback* aCallback,
|
||||
uint32_t aFlags,
|
||||
uint32_t aRequestedCount,
|
||||
nsIEventTarget* aTarget)
|
||||
{
|
||||
if (aFlags != 0) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
if (mCallback || mCallbackTarget) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
mCallbackTarget = aTarget;
|
||||
// 0 means "any number of bytes except 0"
|
||||
mNotifyThreshold = aRequestedCount;
|
||||
if (!aRequestedCount) {
|
||||
mNotifyThreshold = 1024; // We don't want to notify incessantly
|
||||
}
|
||||
|
||||
// We set the callback absolutely last, because NotifyListener uses it to
|
||||
// determine if someone needs to be notified. If we don't set it last,
|
||||
// NotifyListener might try to fire off a notification to a null target
|
||||
// which will generally cause non-threadsafe objects to be used off the
|
||||
// main thread
|
||||
mCallback = aCallback;
|
||||
|
||||
// What we are being asked for may be present already
|
||||
NotifyListener();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsBMPEncoder::CloseWithStatus(nsresult aStatus)
|
||||
{
|
||||
return Close();
|
||||
}
|
||||
|
||||
// nsBMPEncoder::ConvertHostARGBRow
|
||||
//
|
||||
// Our colors are stored with premultiplied alphas, but we need
|
||||
// an output with no alpha in machine-independent byte order.
|
||||
//
|
||||
void
|
||||
nsBMPEncoder::ConvertHostARGBRow(const uint8_t* aSrc,
|
||||
const UniquePtr<uint8_t[]>& aDest,
|
||||
uint32_t aPixelWidth)
|
||||
{
|
||||
uint16_t bytes = BytesPerPixel(mBMPInfoHeader.bpp);
|
||||
|
||||
if (mBMPInfoHeader.bpp == 32) {
|
||||
for (uint32_t x = 0; x < aPixelWidth; x++) {
|
||||
const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x];
|
||||
uint8_t* pixelOut = &aDest[x * bytes];
|
||||
|
||||
pixelOut[0] = (pixelIn & 0x00ff0000) >> 16;
|
||||
pixelOut[1] = (pixelIn & 0x0000ff00) >> 8;
|
||||
pixelOut[2] = (pixelIn & 0x000000ff) >> 0;
|
||||
pixelOut[3] = (pixelIn & 0xff000000) >> 24;
|
||||
}
|
||||
} else {
|
||||
for (uint32_t x = 0; x < aPixelWidth; x++) {
|
||||
const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x];
|
||||
uint8_t* pixelOut = &aDest[x * bytes];
|
||||
|
||||
pixelOut[0] = (pixelIn & 0xff0000) >> 16;
|
||||
pixelOut[1] = (pixelIn & 0x00ff00) >> 8;
|
||||
pixelOut[2] = (pixelIn & 0x0000ff) >> 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsBMPEncoder::NotifyListener()
|
||||
{
|
||||
if (mCallback &&
|
||||
(GetCurrentImageBufferOffset() - mImageBufferReadPoint >=
|
||||
mNotifyThreshold || mFinished)) {
|
||||
nsCOMPtr<nsIInputStreamCallback> callback;
|
||||
if (mCallbackTarget) {
|
||||
callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
|
||||
} else {
|
||||
callback = mCallback;
|
||||
}
|
||||
|
||||
NS_ASSERTION(callback, "Shouldn't fail to make the callback");
|
||||
// Null the callback first because OnInputStreamReady could
|
||||
// reenter AsyncWait
|
||||
mCallback = nullptr;
|
||||
mCallbackTarget = nullptr;
|
||||
mNotifyThreshold = 0;
|
||||
|
||||
callback->OnInputStreamReady(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes the BMP file header mBMPFileHeader to the passed in values
|
||||
nsresult
|
||||
nsBMPEncoder::InitFileHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth,
|
||||
uint32_t aHeight)
|
||||
{
|
||||
memset(&mBMPFileHeader, 0, sizeof(mBMPFileHeader));
|
||||
mBMPFileHeader.signature[0] = 'B';
|
||||
mBMPFileHeader.signature[1] = 'M';
|
||||
|
||||
if (aVersion == VERSION_3) {
|
||||
mBMPFileHeader.dataoffset = FILE_HEADER_LENGTH + InfoHeaderLength::WIN_V3;
|
||||
} else { // aVersion == 5
|
||||
mBMPFileHeader.dataoffset = FILE_HEADER_LENGTH + InfoHeaderLength::WIN_V5;
|
||||
}
|
||||
|
||||
// The color table is present only if BPP is <= 8
|
||||
if (aBPP <= 8) {
|
||||
uint32_t numColors = 1 << aBPP;
|
||||
mBMPFileHeader.dataoffset += 4 * numColors;
|
||||
CheckedUint32 filesize =
|
||||
CheckedUint32(mBMPFileHeader.dataoffset) + CheckedUint32(aWidth) * aHeight;
|
||||
if (MOZ_UNLIKELY(!filesize.isValid())) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mBMPFileHeader.filesize = filesize.value();
|
||||
} else {
|
||||
CheckedUint32 filesize =
|
||||
CheckedUint32(mBMPFileHeader.dataoffset) +
|
||||
(CheckedUint32(aWidth) * BytesPerPixel(aBPP) + PaddingBytes(aBPP, aWidth)) * aHeight;
|
||||
if (MOZ_UNLIKELY(!filesize.isValid())) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mBMPFileHeader.filesize = filesize.value();
|
||||
}
|
||||
|
||||
mBMPFileHeader.reserved = 0;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#define ENCODE(pImageBufferCurr, value) \
|
||||
memcpy(*pImageBufferCurr, &value, sizeof value); \
|
||||
*pImageBufferCurr += sizeof value;
|
||||
|
||||
// Initializes the bitmap info header mBMPInfoHeader to the passed in values
|
||||
nsresult
|
||||
nsBMPEncoder::InitInfoHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth,
|
||||
uint32_t aHeight)
|
||||
{
|
||||
memset(&mBMPInfoHeader, 0, sizeof(mBMPInfoHeader));
|
||||
if (aVersion == VERSION_3) {
|
||||
mBMPInfoHeader.bihsize = InfoHeaderLength::WIN_V3;
|
||||
} else {
|
||||
MOZ_ASSERT(aVersion == VERSION_5);
|
||||
mBMPInfoHeader.bihsize = InfoHeaderLength::WIN_V5;
|
||||
}
|
||||
|
||||
CheckedInt32 width(aWidth);
|
||||
CheckedInt32 height(aHeight);
|
||||
if (MOZ_UNLIKELY(!width.isValid() || !height.isValid())) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mBMPInfoHeader.width = width.value();
|
||||
mBMPInfoHeader.height = height.value();
|
||||
|
||||
mBMPInfoHeader.planes = 1;
|
||||
mBMPInfoHeader.bpp = aBPP;
|
||||
mBMPInfoHeader.compression = 0;
|
||||
mBMPInfoHeader.colors = 0;
|
||||
mBMPInfoHeader.important_colors = 0;
|
||||
|
||||
CheckedUint32 check = CheckedUint32(aWidth) * BytesPerPixel(aBPP);
|
||||
if (MOZ_UNLIKELY(!check.isValid())) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (aBPP <= 8) {
|
||||
CheckedUint32 imagesize = CheckedUint32(aWidth) * aHeight;
|
||||
if (MOZ_UNLIKELY(!imagesize.isValid())) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mBMPInfoHeader.image_size = imagesize.value();
|
||||
} else {
|
||||
CheckedUint32 imagesize =
|
||||
(CheckedUint32(aWidth) * BytesPerPixel(aBPP) + PaddingBytes(aBPP, aWidth)) * CheckedUint32(aHeight);
|
||||
if (MOZ_UNLIKELY(!imagesize.isValid())) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mBMPInfoHeader.image_size = imagesize.value();
|
||||
}
|
||||
mBMPInfoHeader.xppm = 0;
|
||||
mBMPInfoHeader.yppm = 0;
|
||||
if (aVersion >= VERSION_5) {
|
||||
mBMPInfoHeader.red_mask = 0x000000FF;
|
||||
mBMPInfoHeader.green_mask = 0x0000FF00;
|
||||
mBMPInfoHeader.blue_mask = 0x00FF0000;
|
||||
mBMPInfoHeader.alpha_mask = 0xFF000000;
|
||||
mBMPInfoHeader.color_space = V5InfoHeader::COLOR_SPACE_LCS_SRGB;
|
||||
mBMPInfoHeader.white_point.r.x = 0;
|
||||
mBMPInfoHeader.white_point.r.y = 0;
|
||||
mBMPInfoHeader.white_point.r.z = 0;
|
||||
mBMPInfoHeader.white_point.g.x = 0;
|
||||
mBMPInfoHeader.white_point.g.y = 0;
|
||||
mBMPInfoHeader.white_point.g.z = 0;
|
||||
mBMPInfoHeader.white_point.b.x = 0;
|
||||
mBMPInfoHeader.white_point.b.y = 0;
|
||||
mBMPInfoHeader.white_point.b.z = 0;
|
||||
mBMPInfoHeader.gamma_red = 0;
|
||||
mBMPInfoHeader.gamma_green = 0;
|
||||
mBMPInfoHeader.gamma_blue = 0;
|
||||
mBMPInfoHeader.intent = 0;
|
||||
mBMPInfoHeader.profile_offset = 0;
|
||||
mBMPInfoHeader.profile_size = 0;
|
||||
mBMPInfoHeader.reserved = 0;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Encodes the BMP file header mBMPFileHeader
|
||||
void
|
||||
nsBMPEncoder::EncodeFileHeader()
|
||||
{
|
||||
FileHeader littleEndianBFH = mBMPFileHeader;
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianBFH.filesize, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianBFH.reserved, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianBFH.dataoffset, 1);
|
||||
|
||||
ENCODE(&mImageBufferCurr, littleEndianBFH.signature);
|
||||
ENCODE(&mImageBufferCurr, littleEndianBFH.filesize);
|
||||
ENCODE(&mImageBufferCurr, littleEndianBFH.reserved);
|
||||
ENCODE(&mImageBufferCurr, littleEndianBFH.dataoffset);
|
||||
}
|
||||
|
||||
// Encodes the BMP infor header mBMPInfoHeader
|
||||
void
|
||||
nsBMPEncoder::EncodeInfoHeader()
|
||||
{
|
||||
V5InfoHeader littleEndianmBIH = mBMPInfoHeader;
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.bihsize, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.width, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.height, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.planes, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.bpp, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.compression, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.image_size, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.xppm, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.yppm, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.colors, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.important_colors,
|
||||
1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.red_mask, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.green_mask, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.blue_mask, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.alpha_mask, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.color_space, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.r.x, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.r.y, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.r.z, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.g.x, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.g.y, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.g.z, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.b.x, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.b.y, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.b.z, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.gamma_red, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.gamma_green, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.gamma_blue, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.intent, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.profile_offset, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.profile_size, 1);
|
||||
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.bihsize);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.width);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.height);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.planes);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.bpp);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.compression);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.image_size);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.xppm);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.yppm);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.colors);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.important_colors);
|
||||
|
||||
if (mBMPInfoHeader.bihsize > InfoHeaderLength::WIN_V3) {
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.red_mask);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.green_mask);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.blue_mask);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.alpha_mask);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.color_space);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.r.x);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.r.y);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.r.z);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.g.x);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.g.y);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.g.z);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.b.x);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.b.y);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.b.z);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.gamma_red);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.gamma_green);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.gamma_blue);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.intent);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.profile_offset);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.profile_size);
|
||||
ENCODE(&mImageBufferCurr, littleEndianmBIH.reserved);
|
||||
}
|
||||
}
|
||||
|
||||
// Sets a pixel in the image buffer that doesn't have alpha data
|
||||
static inline void
|
||||
SetPixel24(uint8_t*& imageBufferCurr, uint8_t aRed, uint8_t aGreen,
|
||||
uint8_t aBlue)
|
||||
{
|
||||
*imageBufferCurr = aBlue;
|
||||
*(imageBufferCurr + 1) = aGreen;
|
||||
*(imageBufferCurr + 2) = aRed;
|
||||
}
|
||||
|
||||
// Sets a pixel in the image buffer with alpha data
|
||||
static inline void
|
||||
SetPixel32(uint8_t*& imageBufferCurr, uint8_t aRed, uint8_t aGreen,
|
||||
uint8_t aBlue, uint8_t aAlpha = 0xFF)
|
||||
{
|
||||
*imageBufferCurr = aBlue;
|
||||
*(imageBufferCurr + 1) = aGreen;
|
||||
*(imageBufferCurr + 2) = aRed;
|
||||
*(imageBufferCurr + 3) = aAlpha;
|
||||
}
|
||||
|
||||
// Encodes a row of image data which does not have alpha data
|
||||
void
|
||||
nsBMPEncoder::EncodeImageDataRow24(const uint8_t* aData)
|
||||
{
|
||||
for (int32_t x = 0; x < mBMPInfoHeader.width; x++) {
|
||||
uint32_t pos = x * BytesPerPixel(mBMPInfoHeader.bpp);
|
||||
SetPixel24(mImageBufferCurr, aData[pos], aData[pos + 1], aData[pos + 2]);
|
||||
mImageBufferCurr += BytesPerPixel(mBMPInfoHeader.bpp);
|
||||
}
|
||||
|
||||
for (uint32_t x = 0; x < PaddingBytes(mBMPInfoHeader.bpp,
|
||||
mBMPInfoHeader.width); x++) {
|
||||
*mImageBufferCurr++ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Encodes a row of image data which does have alpha data
|
||||
void
|
||||
nsBMPEncoder::EncodeImageDataRow32(const uint8_t* aData)
|
||||
{
|
||||
for (int32_t x = 0; x < mBMPInfoHeader.width; x++) {
|
||||
uint32_t pos = x * BytesPerPixel(mBMPInfoHeader.bpp);
|
||||
SetPixel32(mImageBufferCurr, aData[pos], aData[pos + 1],
|
||||
aData[pos + 2], aData[pos + 3]);
|
||||
mImageBufferCurr += 4;
|
||||
}
|
||||
|
||||
for (uint32_t x = 0; x < PaddingBytes(mBMPInfoHeader.bpp,
|
||||
mBMPInfoHeader.width); x++) {
|
||||
*mImageBufferCurr++ = 0;
|
||||
}
|
||||
}
|
||||
157
image/encoders/bmp/nsBMPEncoder.h
Normal file
157
image/encoders/bmp/nsBMPEncoder.h
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_image_encoders_bmp_nsBMPEncoder_h
|
||||
#define mozilla_image_encoders_bmp_nsBMPEncoder_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
#include "imgIEncoder.h"
|
||||
#include "BMPHeaders.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
#define NS_BMPENCODER_CID \
|
||||
{ /* 13a5320c-4c91-4FA4-bd16-b081a3ba8c0b */ \
|
||||
0x13a5320c, \
|
||||
0x4c91, \
|
||||
0x4fa4, \
|
||||
{0xbd, 0x16, 0xb0, 0x81, 0xa3, 0Xba, 0x8c, 0x0b} \
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
namespace bmp {
|
||||
|
||||
struct FileHeader {
|
||||
char signature[2]; // String "BM".
|
||||
uint32_t filesize; // File size.
|
||||
int32_t reserved; // Zero.
|
||||
uint32_t dataoffset; // Offset to raster data.
|
||||
};
|
||||
|
||||
struct XYZ {
|
||||
int32_t x, y, z;
|
||||
};
|
||||
|
||||
struct XYZTriple {
|
||||
XYZ r, g, b;
|
||||
};
|
||||
|
||||
struct V5InfoHeader {
|
||||
uint32_t bihsize; // Header size
|
||||
int32_t width; // Uint16 in OS/2 BMPs
|
||||
int32_t height; // Uint16 in OS/2 BMPs
|
||||
uint16_t planes; // =1
|
||||
uint16_t bpp; // Bits per pixel.
|
||||
uint32_t compression; // See Compression for valid values
|
||||
uint32_t image_size; // (compressed) image size. Can be 0 if
|
||||
// compression==0
|
||||
uint32_t xppm; // Pixels per meter, horizontal
|
||||
uint32_t yppm; // Pixels per meter, vertical
|
||||
uint32_t colors; // Used Colors
|
||||
uint32_t important_colors; // Number of important colors. 0=all
|
||||
// The rest of the header is not available in WIN_V3 BMP Files
|
||||
uint32_t red_mask; // Bits used for red component
|
||||
uint32_t green_mask; // Bits used for green component
|
||||
uint32_t blue_mask; // Bits used for blue component
|
||||
uint32_t alpha_mask; // Bits used for alpha component
|
||||
uint32_t color_space; // 0x73524742=LCS_sRGB ...
|
||||
// These members are unused unless color_space == LCS_CALIBRATED_RGB
|
||||
XYZTriple white_point; // Logical white point
|
||||
uint32_t gamma_red; // Red gamma component
|
||||
uint32_t gamma_green; // Green gamma component
|
||||
uint32_t gamma_blue; // Blue gamma component
|
||||
uint32_t intent; // Rendering intent
|
||||
// These members are unused unless color_space == LCS_PROFILE_*
|
||||
uint32_t profile_offset; // Offset to profile data in bytes
|
||||
uint32_t profile_size; // Size of profile data in bytes
|
||||
uint32_t reserved; // =0
|
||||
|
||||
static const uint32_t COLOR_SPACE_LCS_SRGB = 0x73524742;
|
||||
};
|
||||
|
||||
} // namespace bmp
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
// Provides BMP encoding functionality. Use InitFromData() to do the
|
||||
// encoding. See that function definition for encoding options.
|
||||
|
||||
class nsBMPEncoder final : public imgIEncoder
|
||||
{
|
||||
typedef mozilla::ReentrantMonitor ReentrantMonitor;
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_IMGIENCODER
|
||||
NS_DECL_NSIINPUTSTREAM
|
||||
NS_DECL_NSIASYNCINPUTSTREAM
|
||||
|
||||
nsBMPEncoder();
|
||||
|
||||
protected:
|
||||
~nsBMPEncoder();
|
||||
|
||||
enum Version
|
||||
{
|
||||
VERSION_3 = 3,
|
||||
VERSION_5 = 5
|
||||
};
|
||||
|
||||
// See InitData in the cpp for valid parse options
|
||||
nsresult ParseOptions(const nsAString& aOptions, Version& aVersionOut,
|
||||
uint16_t& aBppOut);
|
||||
// Obtains data with no alpha in machine-independent byte order
|
||||
void ConvertHostARGBRow(const uint8_t* aSrc,
|
||||
const mozilla::UniquePtr<uint8_t[]>& aDest,
|
||||
uint32_t aPixelWidth);
|
||||
// Thread safe notify listener
|
||||
void NotifyListener();
|
||||
|
||||
// Initializes the bitmap file header member mBMPFileHeader
|
||||
nsresult InitFileHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth,
|
||||
uint32_t aHeight);
|
||||
// Initializes the bitmap info header member mBMPInfoHeader
|
||||
nsresult InitInfoHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth,
|
||||
uint32_t aHeight);
|
||||
|
||||
// Encodes the bitmap file header member mBMPFileHeader
|
||||
void EncodeFileHeader();
|
||||
// Encodes the bitmap info header member mBMPInfoHeader
|
||||
void EncodeInfoHeader();
|
||||
// Encodes a row of image data which does not have alpha data
|
||||
void EncodeImageDataRow24(const uint8_t* aData);
|
||||
// Encodes a row of image data which does have alpha data
|
||||
void EncodeImageDataRow32(const uint8_t* aData);
|
||||
// Obtains the current offset filled up to for the image buffer
|
||||
inline int32_t GetCurrentImageBufferOffset()
|
||||
{
|
||||
return static_cast<int32_t>(mImageBufferCurr - mImageBufferStart);
|
||||
}
|
||||
|
||||
// These headers will always contain endian independent stuff
|
||||
// They store the BMP headers which will be encoded
|
||||
mozilla::image::bmp::FileHeader mBMPFileHeader;
|
||||
mozilla::image::bmp::V5InfoHeader mBMPInfoHeader;
|
||||
|
||||
// Keeps track of the start of the image buffer
|
||||
uint8_t* mImageBufferStart;
|
||||
// Keeps track of the current position in the image buffer
|
||||
uint8_t* mImageBufferCurr;
|
||||
// Keeps track of the image buffer size
|
||||
uint32_t mImageBufferSize;
|
||||
// Keeps track of the number of bytes in the image buffer which are read
|
||||
uint32_t mImageBufferReadPoint;
|
||||
// Stores true if the image is done being encoded
|
||||
bool mFinished;
|
||||
|
||||
nsCOMPtr<nsIInputStreamCallback> mCallback;
|
||||
nsCOMPtr<nsIEventTarget> mCallbackTarget;
|
||||
uint32_t mNotifyThreshold;
|
||||
};
|
||||
|
||||
#endif // mozilla_image_encoders_bmp_nsBMPEncoder_h
|
||||
18
image/encoders/ico/moz.build
Normal file
18
image/encoders/ico/moz.build
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'nsICOEncoder.cpp',
|
||||
]
|
||||
|
||||
# Decoders need RasterImage.h
|
||||
LOCAL_INCLUDES += [
|
||||
'/image',
|
||||
'/image/encoders/bmp',
|
||||
'/image/encoders/png',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
542
image/encoders/ico/nsICOEncoder.cpp
Normal file
542
image/encoders/ico/nsICOEncoder.cpp
Normal file
|
|
@ -0,0 +1,542 @@
|
|||
/* 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 "nsCRT.h"
|
||||
#include "mozilla/EndianUtils.h"
|
||||
#include "nsBMPEncoder.h"
|
||||
#include "nsPNGEncoder.h"
|
||||
#include "nsICOEncoder.h"
|
||||
#include "prprf.h"
|
||||
#include "nsString.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsTArray.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::image;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsICOEncoder, imgIEncoder, nsIInputStream,
|
||||
nsIAsyncInputStream)
|
||||
|
||||
nsICOEncoder::nsICOEncoder() : mImageBufferStart(nullptr),
|
||||
mImageBufferCurr(0),
|
||||
mImageBufferSize(0),
|
||||
mImageBufferReadPoint(0),
|
||||
mFinished(false),
|
||||
mUsePNG(true),
|
||||
mNotifyThreshold(0)
|
||||
{
|
||||
}
|
||||
|
||||
nsICOEncoder::~nsICOEncoder()
|
||||
{
|
||||
if (mImageBufferStart) {
|
||||
free(mImageBufferStart);
|
||||
mImageBufferStart = nullptr;
|
||||
mImageBufferCurr = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// nsICOEncoder::InitFromData
|
||||
// Two output options are supported: format=<png|bmp>;bpp=<bpp_value>
|
||||
// format specifies whether to use png or bitmap format
|
||||
// bpp specifies the bits per pixel to use where bpp_value can be 24 or 32
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::InitFromData(const uint8_t* aData,
|
||||
uint32_t aLength,
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aStride,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
// validate input format
|
||||
if (aInputFormat != INPUT_FORMAT_RGB &&
|
||||
aInputFormat != INPUT_FORMAT_RGBA &&
|
||||
aInputFormat != INPUT_FORMAT_HOSTARGB) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Stride is the padded width of each row, so it better be longer
|
||||
if ((aInputFormat == INPUT_FORMAT_RGB &&
|
||||
aStride < aWidth * 3) ||
|
||||
((aInputFormat == INPUT_FORMAT_RGBA ||
|
||||
aInputFormat == INPUT_FORMAT_HOSTARGB) &&
|
||||
aStride < aWidth * 4)) {
|
||||
NS_WARNING("Invalid stride for InitFromData");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride,
|
||||
aInputFormat, aOutputOptions);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = EndImageEncode();
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Returns the number of bytes in the image buffer used
|
||||
// For an ICO file, this is all bytes in the buffer.
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::GetImageBufferUsed(uint32_t* aOutputSize)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aOutputSize);
|
||||
*aOutputSize = mImageBufferSize;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Returns a pointer to the start of the image buffer
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::GetImageBuffer(char** aOutputBuffer)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aOutputBuffer);
|
||||
*aOutputBuffer = reinterpret_cast<char*>(mImageBufferStart);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::AddImageFrame(const uint8_t* aData,
|
||||
uint32_t aLength,
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aStride,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aFrameOptions)
|
||||
{
|
||||
if (mUsePNG) {
|
||||
|
||||
mContainedEncoder = new nsPNGEncoder();
|
||||
nsresult rv;
|
||||
nsAutoString noParams;
|
||||
rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight,
|
||||
aStride, aInputFormat, noParams);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint32_t PNGImageBufferSize;
|
||||
mContainedEncoder->GetImageBufferUsed(&PNGImageBufferSize);
|
||||
mImageBufferSize = ICONFILEHEADERSIZE + ICODIRENTRYSIZE +
|
||||
PNGImageBufferSize;
|
||||
mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize));
|
||||
if (!mImageBufferStart) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
mImageBufferCurr = mImageBufferStart;
|
||||
mICODirEntry.mBytesInRes = PNGImageBufferSize;
|
||||
|
||||
EncodeFileHeader();
|
||||
EncodeInfoHeader();
|
||||
|
||||
char* imageBuffer;
|
||||
rv = mContainedEncoder->GetImageBuffer(&imageBuffer);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
memcpy(mImageBufferCurr, imageBuffer, PNGImageBufferSize);
|
||||
mImageBufferCurr += PNGImageBufferSize;
|
||||
} else {
|
||||
mContainedEncoder = new nsBMPEncoder();
|
||||
nsresult rv;
|
||||
|
||||
nsAutoString params;
|
||||
params.AppendLiteral("bpp=");
|
||||
params.AppendInt(mICODirEntry.mBitCount);
|
||||
|
||||
rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight,
|
||||
aStride, aInputFormat, params);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
uint32_t andMaskSize = ((GetRealWidth() + 31) / 32) * 4 * // row AND mask
|
||||
GetRealHeight(); // num rows
|
||||
|
||||
uint32_t BMPImageBufferSize;
|
||||
mContainedEncoder->GetImageBufferUsed(&BMPImageBufferSize);
|
||||
mImageBufferSize = ICONFILEHEADERSIZE + ICODIRENTRYSIZE +
|
||||
BMPImageBufferSize + andMaskSize;
|
||||
mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize));
|
||||
if (!mImageBufferStart) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
mImageBufferCurr = mImageBufferStart;
|
||||
|
||||
// Icon files that wrap a BMP file must not include the BITMAPFILEHEADER
|
||||
// section at the beginning of the encoded BMP data, so we must skip over
|
||||
// bmp::FILE_HEADER_LENGTH bytes when adding the BMP content to the icon
|
||||
// file.
|
||||
mICODirEntry.mBytesInRes =
|
||||
BMPImageBufferSize - bmp::FILE_HEADER_LENGTH + andMaskSize;
|
||||
|
||||
// Encode the icon headers
|
||||
EncodeFileHeader();
|
||||
EncodeInfoHeader();
|
||||
|
||||
char* imageBuffer;
|
||||
rv = mContainedEncoder->GetImageBuffer(&imageBuffer);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
memcpy(mImageBufferCurr, imageBuffer + bmp::FILE_HEADER_LENGTH,
|
||||
BMPImageBufferSize - bmp::FILE_HEADER_LENGTH);
|
||||
// We need to fix the BMP height to be *2 for the AND mask
|
||||
uint32_t fixedHeight = GetRealHeight() * 2;
|
||||
NativeEndian::swapToLittleEndianInPlace(&fixedHeight, 1);
|
||||
// The height is stored at an offset of 8 from the DIB header
|
||||
memcpy(mImageBufferCurr + 8, &fixedHeight, sizeof(fixedHeight));
|
||||
mImageBufferCurr += BMPImageBufferSize - bmp::FILE_HEADER_LENGTH;
|
||||
|
||||
// Calculate rowsize in DWORD's
|
||||
uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
|
||||
int32_t currentLine = GetRealHeight();
|
||||
|
||||
// Write out the AND mask
|
||||
while (currentLine > 0) {
|
||||
currentLine--;
|
||||
uint8_t* encoded = mImageBufferCurr + currentLine * rowSize;
|
||||
uint8_t* encodedEnd = encoded + rowSize;
|
||||
while (encoded != encodedEnd) {
|
||||
*encoded = 0; // make everything visible
|
||||
encoded++;
|
||||
}
|
||||
}
|
||||
|
||||
mImageBufferCurr += andMaskSize;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// See ::InitFromData for other info.
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::StartImageEncode(uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
// can't initialize more than once
|
||||
if (mImageBufferStart || mImageBufferCurr) {
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
// validate input format
|
||||
if (aInputFormat != INPUT_FORMAT_RGB &&
|
||||
aInputFormat != INPUT_FORMAT_RGBA &&
|
||||
aInputFormat != INPUT_FORMAT_HOSTARGB) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Icons are only 1 byte, so make sure our bitmap is in range
|
||||
if (aWidth > 256 || aHeight > 256) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// parse and check any provided output options
|
||||
uint16_t bpp = 24;
|
||||
bool usePNG = true;
|
||||
nsresult rv = ParseOptions(aOutputOptions, bpp, usePNG);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
MOZ_ASSERT(bpp <= 32);
|
||||
|
||||
mUsePNG = usePNG;
|
||||
|
||||
InitFileHeader();
|
||||
// The width and height are stored as 0 when we have a value of 256
|
||||
InitInfoHeader(bpp, aWidth == 256 ? 0 : (uint8_t)aWidth,
|
||||
aHeight == 256 ? 0 : (uint8_t)aHeight);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::EndImageEncode()
|
||||
{
|
||||
// must be initialized
|
||||
if (!mImageBufferStart || !mImageBufferCurr) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
mFinished = true;
|
||||
NotifyListener();
|
||||
|
||||
// if output callback can't get enough memory, it will free our buffer
|
||||
if (!mImageBufferStart || !mImageBufferCurr) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Parses the encoder options and sets the bits per pixel to use and PNG or BMP
|
||||
// See InitFromData for a description of the parse options
|
||||
nsresult
|
||||
nsICOEncoder::ParseOptions(const nsAString& aOptions, uint16_t& aBppOut,
|
||||
bool& aUsePNGOut)
|
||||
{
|
||||
// If no parsing options just use the default of 24BPP and PNG yes
|
||||
if (aOptions.Length() == 0) {
|
||||
aUsePNGOut = true;
|
||||
aBppOut = 24;
|
||||
}
|
||||
|
||||
// Parse the input string into a set of name/value pairs.
|
||||
// From format: format=<png|bmp>;bpp=<bpp_value>
|
||||
// to format: [0] = format=<png|bmp>, [1] = bpp=<bpp_value>
|
||||
nsTArray<nsCString> nameValuePairs;
|
||||
if (!ParseString(NS_ConvertUTF16toUTF8(aOptions), ';', nameValuePairs)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// For each name/value pair in the set
|
||||
for (unsigned i = 0; i < nameValuePairs.Length(); ++i) {
|
||||
|
||||
// Split the name value pair [0] = name, [1] = value
|
||||
nsTArray<nsCString> nameValuePair;
|
||||
if (!ParseString(nameValuePairs[i], '=', nameValuePair)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (nameValuePair.Length() != 2) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// Parse the format portion of the string format=<png|bmp>;bpp=<bpp_value>
|
||||
if (nameValuePair[0].Equals("format",
|
||||
nsCaseInsensitiveCStringComparator())) {
|
||||
if (nameValuePair[1].Equals("png",
|
||||
nsCaseInsensitiveCStringComparator())) {
|
||||
aUsePNGOut = true;
|
||||
}
|
||||
else if (nameValuePair[1].Equals("bmp",
|
||||
nsCaseInsensitiveCStringComparator())) {
|
||||
aUsePNGOut = false;
|
||||
}
|
||||
else {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the bpp portion of the string format=<png|bmp>;bpp=<bpp_value>
|
||||
if (nameValuePair[0].Equals("bpp", nsCaseInsensitiveCStringComparator())) {
|
||||
if (nameValuePair[1].EqualsLiteral("24")) {
|
||||
aBppOut = 24;
|
||||
}
|
||||
else if (nameValuePair[1].EqualsLiteral("32")) {
|
||||
aBppOut = 32;
|
||||
}
|
||||
else {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::Close()
|
||||
{
|
||||
if (mImageBufferStart) {
|
||||
free(mImageBufferStart);
|
||||
mImageBufferStart = nullptr;
|
||||
mImageBufferSize = 0;
|
||||
mImageBufferReadPoint = 0;
|
||||
mImageBufferCurr = nullptr;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Obtains the available bytes to read
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::Available(uint64_t *_retval)
|
||||
{
|
||||
if (!mImageBufferStart || !mImageBufferCurr) {
|
||||
return NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
*_retval = GetCurrentImageBufferOffset() - mImageBufferReadPoint;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// [noscript] Reads bytes which are available
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
|
||||
{
|
||||
return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
|
||||
}
|
||||
|
||||
// [noscript] Reads segments
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
|
||||
uint32_t aCount, uint32_t* _retval)
|
||||
{
|
||||
uint32_t maxCount = GetCurrentImageBufferOffset() - mImageBufferReadPoint;
|
||||
if (maxCount == 0) {
|
||||
*_retval = 0;
|
||||
return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
|
||||
}
|
||||
|
||||
if (aCount > maxCount) {
|
||||
aCount = maxCount;
|
||||
}
|
||||
|
||||
nsresult rv = aWriter(this, aClosure,
|
||||
reinterpret_cast<const char*>(mImageBufferStart +
|
||||
mImageBufferReadPoint),
|
||||
0, aCount, _retval);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
NS_ASSERTION(*_retval <= aCount, "bad write count");
|
||||
mImageBufferReadPoint += *_retval;
|
||||
}
|
||||
// errors returned from the writer end here!
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::IsNonBlocking(bool* _retval)
|
||||
{
|
||||
*_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::AsyncWait(nsIInputStreamCallback* aCallback,
|
||||
uint32_t aFlags,
|
||||
uint32_t aRequestedCount,
|
||||
nsIEventTarget* aTarget)
|
||||
{
|
||||
if (aFlags != 0) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
if (mCallback || mCallbackTarget) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
mCallbackTarget = aTarget;
|
||||
// 0 means "any number of bytes except 0"
|
||||
mNotifyThreshold = aRequestedCount;
|
||||
if (!aRequestedCount) {
|
||||
mNotifyThreshold = 1024; // We don't want to notify incessantly
|
||||
}
|
||||
|
||||
// We set the callback absolutely last, because NotifyListener uses it to
|
||||
// determine if someone needs to be notified. If we don't set it last,
|
||||
// NotifyListener might try to fire off a notification to a null target
|
||||
// which will generally cause non-threadsafe objects to be used off the
|
||||
// main thread
|
||||
mCallback = aCallback;
|
||||
|
||||
// What we are being asked for may be present already
|
||||
NotifyListener();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsICOEncoder::CloseWithStatus(nsresult aStatus)
|
||||
{
|
||||
return Close();
|
||||
}
|
||||
|
||||
void
|
||||
nsICOEncoder::NotifyListener()
|
||||
{
|
||||
if (mCallback &&
|
||||
(GetCurrentImageBufferOffset() -
|
||||
mImageBufferReadPoint >= mNotifyThreshold || mFinished)) {
|
||||
nsCOMPtr<nsIInputStreamCallback> callback;
|
||||
if (mCallbackTarget) {
|
||||
callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
|
||||
} else {
|
||||
callback = mCallback;
|
||||
}
|
||||
|
||||
NS_ASSERTION(callback, "Shouldn't fail to make the callback");
|
||||
// Null the callback first because OnInputStreamReady could reenter
|
||||
// AsyncWait
|
||||
mCallback = nullptr;
|
||||
mCallbackTarget = nullptr;
|
||||
mNotifyThreshold = 0;
|
||||
|
||||
callback->OnInputStreamReady(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes the icon file header mICOFileHeader
|
||||
void
|
||||
nsICOEncoder::InitFileHeader()
|
||||
{
|
||||
memset(&mICOFileHeader, 0, sizeof(mICOFileHeader));
|
||||
mICOFileHeader.mReserved = 0;
|
||||
mICOFileHeader.mType = 1;
|
||||
mICOFileHeader.mCount = 1;
|
||||
}
|
||||
|
||||
// Initializes the icon directory info header mICODirEntry
|
||||
void
|
||||
nsICOEncoder::InitInfoHeader(uint16_t aBPP, uint8_t aWidth, uint8_t aHeight)
|
||||
{
|
||||
memset(&mICODirEntry, 0, sizeof(mICODirEntry));
|
||||
mICODirEntry.mBitCount = aBPP;
|
||||
mICODirEntry.mBytesInRes = 0;
|
||||
mICODirEntry.mColorCount = 0;
|
||||
mICODirEntry.mWidth = aWidth;
|
||||
mICODirEntry.mHeight = aHeight;
|
||||
mICODirEntry.mImageOffset = ICONFILEHEADERSIZE + ICODIRENTRYSIZE;
|
||||
mICODirEntry.mPlanes = 1;
|
||||
mICODirEntry.mReserved = 0;
|
||||
}
|
||||
|
||||
// Encodes the icon file header mICOFileHeader
|
||||
void
|
||||
nsICOEncoder::EncodeFileHeader()
|
||||
{
|
||||
IconFileHeader littleEndianIFH = mICOFileHeader;
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mReserved, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mType, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mCount, 1);
|
||||
|
||||
memcpy(mImageBufferCurr, &littleEndianIFH.mReserved,
|
||||
sizeof(littleEndianIFH.mReserved));
|
||||
mImageBufferCurr += sizeof(littleEndianIFH.mReserved);
|
||||
memcpy(mImageBufferCurr, &littleEndianIFH.mType,
|
||||
sizeof(littleEndianIFH.mType));
|
||||
mImageBufferCurr += sizeof(littleEndianIFH.mType);
|
||||
memcpy(mImageBufferCurr, &littleEndianIFH.mCount,
|
||||
sizeof(littleEndianIFH.mCount));
|
||||
mImageBufferCurr += sizeof(littleEndianIFH.mCount);
|
||||
}
|
||||
|
||||
// Encodes the icon directory info header mICODirEntry
|
||||
void
|
||||
nsICOEncoder::EncodeInfoHeader()
|
||||
{
|
||||
IconDirEntry littleEndianmIDE = mICODirEntry;
|
||||
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mPlanes, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBitCount, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBytesInRes, 1);
|
||||
NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mImageOffset, 1);
|
||||
|
||||
memcpy(mImageBufferCurr, &littleEndianmIDE.mWidth,
|
||||
sizeof(littleEndianmIDE.mWidth));
|
||||
mImageBufferCurr += sizeof(littleEndianmIDE.mWidth);
|
||||
memcpy(mImageBufferCurr, &littleEndianmIDE.mHeight,
|
||||
sizeof(littleEndianmIDE.mHeight));
|
||||
mImageBufferCurr += sizeof(littleEndianmIDE.mHeight);
|
||||
memcpy(mImageBufferCurr, &littleEndianmIDE.mColorCount,
|
||||
sizeof(littleEndianmIDE.mColorCount));
|
||||
mImageBufferCurr += sizeof(littleEndianmIDE.mColorCount);
|
||||
memcpy(mImageBufferCurr, &littleEndianmIDE.mReserved,
|
||||
sizeof(littleEndianmIDE.mReserved));
|
||||
mImageBufferCurr += sizeof(littleEndianmIDE.mReserved);
|
||||
memcpy(mImageBufferCurr, &littleEndianmIDE.mPlanes,
|
||||
sizeof(littleEndianmIDE.mPlanes));
|
||||
mImageBufferCurr += sizeof(littleEndianmIDE.mPlanes);
|
||||
memcpy(mImageBufferCurr, &littleEndianmIDE.mBitCount,
|
||||
sizeof(littleEndianmIDE.mBitCount));
|
||||
mImageBufferCurr += sizeof(littleEndianmIDE.mBitCount);
|
||||
memcpy(mImageBufferCurr, &littleEndianmIDE.mBytesInRes,
|
||||
sizeof(littleEndianmIDE.mBytesInRes));
|
||||
mImageBufferCurr += sizeof(littleEndianmIDE.mBytesInRes);
|
||||
memcpy(mImageBufferCurr, &littleEndianmIDE.mImageOffset,
|
||||
sizeof(littleEndianmIDE.mImageOffset));
|
||||
mImageBufferCurr += sizeof(littleEndianmIDE.mImageOffset);
|
||||
}
|
||||
99
image/encoders/ico/nsICOEncoder.h
Normal file
99
image/encoders/ico/nsICOEncoder.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/* 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/. */
|
||||
|
||||
#ifndef mozilla_image_encoders_ico_nsICOEncoder_h
|
||||
#define mozilla_image_encoders_ico_nsICOEncoder_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
|
||||
#include "imgIEncoder.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "ICOFileHeaders.h"
|
||||
|
||||
#define NS_ICOENCODER_CID \
|
||||
{ /*92AE3AB2-8968-41B1-8709-B6123BCEAF21 */ \
|
||||
0x92ae3ab2, \
|
||||
0x8968, \
|
||||
0x41b1, \
|
||||
{0x87, 0x09, 0xb6, 0x12, 0x3b, 0Xce, 0xaf, 0x21} \
|
||||
}
|
||||
|
||||
// Provides ICO encoding functionality. Use InitFromData() to do the
|
||||
// encoding. See that function definition for encoding options.
|
||||
|
||||
class nsICOEncoder final : public imgIEncoder
|
||||
{
|
||||
typedef mozilla::ReentrantMonitor ReentrantMonitor;
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_IMGIENCODER
|
||||
NS_DECL_NSIINPUTSTREAM
|
||||
NS_DECL_NSIASYNCINPUTSTREAM
|
||||
|
||||
nsICOEncoder();
|
||||
|
||||
// Obtains the width of the icon directory entry
|
||||
uint32_t GetRealWidth() const
|
||||
{
|
||||
return mICODirEntry.mWidth == 0 ? 256 : mICODirEntry.mWidth;
|
||||
}
|
||||
|
||||
// Obtains the height of the icon directory entry
|
||||
uint32_t GetRealHeight() const
|
||||
{
|
||||
return mICODirEntry.mHeight == 0 ? 256 : mICODirEntry.mHeight;
|
||||
}
|
||||
|
||||
protected:
|
||||
~nsICOEncoder();
|
||||
|
||||
nsresult ParseOptions(const nsAString& aOptions, uint16_t& aBppOut,
|
||||
bool& aUsePNGOut);
|
||||
void NotifyListener();
|
||||
|
||||
// Initializes the icon file header mICOFileHeader
|
||||
void InitFileHeader();
|
||||
// Initializes the icon directory info header mICODirEntry
|
||||
void InitInfoHeader(uint16_t aBPP, uint8_t aWidth, uint8_t aHeight);
|
||||
// Encodes the icon file header mICOFileHeader
|
||||
void EncodeFileHeader();
|
||||
// Encodes the icon directory info header mICODirEntry
|
||||
void EncodeInfoHeader();
|
||||
// Obtains the current offset filled up to for the image buffer
|
||||
inline int32_t GetCurrentImageBufferOffset()
|
||||
{
|
||||
return static_cast<int32_t>(mImageBufferCurr - mImageBufferStart);
|
||||
}
|
||||
|
||||
// Holds either a PNG or a BMP depending on the encoding options specified
|
||||
// or if no encoding options specified will use the default (PNG)
|
||||
nsCOMPtr<imgIEncoder> mContainedEncoder;
|
||||
|
||||
// These headers will always contain endian independent stuff.
|
||||
// Don't trust the width and height of mICODirEntry directly,
|
||||
// instead use the accessors GetRealWidth() and GetRealHeight().
|
||||
mozilla::image::IconFileHeader mICOFileHeader;
|
||||
mozilla::image::IconDirEntry mICODirEntry;
|
||||
|
||||
// Keeps track of the start of the image buffer
|
||||
uint8_t* mImageBufferStart;
|
||||
// Keeps track of the current position in the image buffer
|
||||
uint8_t* mImageBufferCurr;
|
||||
// Keeps track of the image buffer size
|
||||
uint32_t mImageBufferSize;
|
||||
// Keeps track of the number of bytes in the image buffer which are read
|
||||
uint32_t mImageBufferReadPoint;
|
||||
// Stores true if the image is done being encoded
|
||||
bool mFinished;
|
||||
// Stores true if the contained image is a PNG
|
||||
bool mUsePNG;
|
||||
|
||||
nsCOMPtr<nsIInputStreamCallback> mCallback;
|
||||
nsCOMPtr<nsIEventTarget> mCallbackTarget;
|
||||
uint32_t mNotifyThreshold;
|
||||
};
|
||||
|
||||
#endif // mozilla_image_encoders_ico_nsICOEncoder_h
|
||||
11
image/encoders/jpeg/moz.build
Normal file
11
image/encoders/jpeg/moz.build
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'nsJPEGEncoder.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
532
image/encoders/jpeg/nsJPEGEncoder.cpp
Normal file
532
image/encoders/jpeg/nsJPEGEncoder.cpp
Normal file
|
|
@ -0,0 +1,532 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#include "nsJPEGEncoder.h"
|
||||
#include "prprf.h"
|
||||
#include "nsString.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "gfxColor.h"
|
||||
|
||||
#include <setjmp.h>
|
||||
#include "jerror.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsJPEGEncoder, imgIEncoder, nsIInputStream,
|
||||
nsIAsyncInputStream)
|
||||
|
||||
// used to pass error info through the JPEG library
|
||||
struct encoder_error_mgr {
|
||||
jpeg_error_mgr pub;
|
||||
jmp_buf setjmp_buffer;
|
||||
};
|
||||
|
||||
nsJPEGEncoder::nsJPEGEncoder()
|
||||
: mFinished(false),
|
||||
mImageBuffer(nullptr),
|
||||
mImageBufferSize(0),
|
||||
mImageBufferUsed(0),
|
||||
mImageBufferReadPoint(0),
|
||||
mCallback(nullptr),
|
||||
mCallbackTarget(nullptr),
|
||||
mNotifyThreshold(0),
|
||||
mReentrantMonitor("nsJPEGEncoder.mReentrantMonitor")
|
||||
{
|
||||
}
|
||||
|
||||
nsJPEGEncoder::~nsJPEGEncoder()
|
||||
{
|
||||
if (mImageBuffer) {
|
||||
free(mImageBuffer);
|
||||
mImageBuffer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// nsJPEGEncoder::InitFromData
|
||||
//
|
||||
// One output option is supported: "quality=X" where X is an integer in the
|
||||
// range 0-100. Higher values for X give better quality.
|
||||
//
|
||||
// Transparency is always discarded.
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::InitFromData(const uint8_t* aData,
|
||||
uint32_t aLength, // (unused, req'd by JS)
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aStride,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
NS_ENSURE_ARG(aData);
|
||||
|
||||
// validate input format
|
||||
if (aInputFormat != INPUT_FORMAT_RGB &&
|
||||
aInputFormat != INPUT_FORMAT_RGBA &&
|
||||
aInputFormat != INPUT_FORMAT_HOSTARGB)
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
||||
// Stride is the padded width of each row, so it better be longer (I'm afraid
|
||||
// people will not understand what stride means, so check it well)
|
||||
if ((aInputFormat == INPUT_FORMAT_RGB &&
|
||||
aStride < aWidth * 3) ||
|
||||
((aInputFormat == INPUT_FORMAT_RGBA ||
|
||||
aInputFormat == INPUT_FORMAT_HOSTARGB) &&
|
||||
aStride < aWidth * 4)) {
|
||||
NS_WARNING("Invalid stride for InitFromData");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// can't initialize more than once
|
||||
if (mImageBuffer != nullptr) {
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
// options: we only have one option so this is easy
|
||||
int quality = 92;
|
||||
if (aOutputOptions.Length() > 0) {
|
||||
// have options string
|
||||
const nsString qualityPrefix(NS_LITERAL_STRING("quality="));
|
||||
if (aOutputOptions.Length() > qualityPrefix.Length() &&
|
||||
StringBeginsWith(aOutputOptions, qualityPrefix)) {
|
||||
// have quality string
|
||||
nsCString value =
|
||||
NS_ConvertUTF16toUTF8(Substring(aOutputOptions,
|
||||
qualityPrefix.Length()));
|
||||
int newquality = -1;
|
||||
if (PR_sscanf(value.get(), "%d", &newquality) == 1) {
|
||||
if (newquality >= 0 && newquality <= 100) {
|
||||
quality = newquality;
|
||||
} else {
|
||||
NS_WARNING("Quality value out of range, should be 0-100,"
|
||||
" using default");
|
||||
}
|
||||
} else {
|
||||
NS_WARNING("Quality value invalid, should be integer 0-100,"
|
||||
" using default");
|
||||
}
|
||||
}
|
||||
else {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
|
||||
jpeg_compress_struct cinfo;
|
||||
|
||||
// We set up the normal JPEG error routines, then override error_exit.
|
||||
// This must be done before the call to create_compress
|
||||
encoder_error_mgr errmgr;
|
||||
cinfo.err = jpeg_std_error(&errmgr.pub);
|
||||
errmgr.pub.error_exit = errorExit;
|
||||
// Establish the setjmp return context for my_error_exit to use.
|
||||
if (setjmp(errmgr.setjmp_buffer)) {
|
||||
// If we get here, the JPEG code has signaled an error.
|
||||
// We need to clean up the JPEG object, close the input file, and return.
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
jpeg_create_compress(&cinfo);
|
||||
cinfo.image_width = aWidth;
|
||||
cinfo.image_height = aHeight;
|
||||
cinfo.input_components = 3;
|
||||
cinfo.in_color_space = JCS_RGB;
|
||||
cinfo.data_precision = 8;
|
||||
|
||||
jpeg_set_defaults(&cinfo);
|
||||
jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100
|
||||
if (quality >= 90) {
|
||||
int i;
|
||||
for (i=0; i < MAX_COMPONENTS; i++) {
|
||||
cinfo.comp_info[i].h_samp_factor=1;
|
||||
cinfo.comp_info[i].v_samp_factor=1;
|
||||
}
|
||||
}
|
||||
|
||||
// set up the destination manager
|
||||
jpeg_destination_mgr destmgr;
|
||||
destmgr.init_destination = initDestination;
|
||||
destmgr.empty_output_buffer = emptyOutputBuffer;
|
||||
destmgr.term_destination = termDestination;
|
||||
cinfo.dest = &destmgr;
|
||||
cinfo.client_data = this;
|
||||
|
||||
jpeg_start_compress(&cinfo, 1);
|
||||
|
||||
// feed it the rows
|
||||
if (aInputFormat == INPUT_FORMAT_RGB) {
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
const uint8_t* row = &aData[cinfo.next_scanline * aStride];
|
||||
jpeg_write_scanlines(&cinfo, const_cast<uint8_t**>(&row), 1);
|
||||
}
|
||||
} else if (aInputFormat == INPUT_FORMAT_RGBA) {
|
||||
UniquePtr<uint8_t[]> rowptr = MakeUnique<uint8_t[]>(aWidth * 3);
|
||||
uint8_t* row = rowptr.get();
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
ConvertRGBARow(&aData[cinfo.next_scanline * aStride], row, aWidth);
|
||||
jpeg_write_scanlines(&cinfo, &row, 1);
|
||||
}
|
||||
} else if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
|
||||
UniquePtr<uint8_t[]> rowptr = MakeUnique<uint8_t[]>(aWidth * 3);
|
||||
uint8_t* row = rowptr.get();
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
ConvertHostARGBRow(&aData[cinfo.next_scanline * aStride], row, aWidth);
|
||||
jpeg_write_scanlines(&cinfo, &row, 1);
|
||||
}
|
||||
}
|
||||
|
||||
jpeg_finish_compress(&cinfo);
|
||||
jpeg_destroy_compress(&cinfo);
|
||||
|
||||
mFinished = true;
|
||||
NotifyListener();
|
||||
|
||||
// if output callback can't get enough memory, it will free our buffer
|
||||
if (!mImageBuffer) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::StartImageEncode(uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
// Returns the number of bytes in the image buffer used.
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::GetImageBufferUsed(uint32_t* aOutputSize)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aOutputSize);
|
||||
*aOutputSize = mImageBufferUsed;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Returns a pointer to the start of the image buffer
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::GetImageBuffer(char** aOutputBuffer)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aOutputBuffer);
|
||||
*aOutputBuffer = reinterpret_cast<char*>(mImageBuffer);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::AddImageFrame(const uint8_t* aData,
|
||||
uint32_t aLength,
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aStride,
|
||||
uint32_t aFrameFormat,
|
||||
const nsAString& aFrameOptions)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::EndImageEncode()
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::Close()
|
||||
{
|
||||
if (mImageBuffer != nullptr) {
|
||||
free(mImageBuffer);
|
||||
mImageBuffer = nullptr;
|
||||
mImageBufferSize = 0;
|
||||
mImageBufferUsed = 0;
|
||||
mImageBufferReadPoint = 0;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::Available(uint64_t* _retval)
|
||||
{
|
||||
if (!mImageBuffer) {
|
||||
return NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
*_retval = mImageBufferUsed - mImageBufferReadPoint;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
|
||||
{
|
||||
return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter,
|
||||
void* aClosure, uint32_t aCount, uint32_t* _retval)
|
||||
{
|
||||
// Avoid another thread reallocing the buffer underneath us
|
||||
ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
|
||||
|
||||
uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint;
|
||||
if (maxCount == 0) {
|
||||
*_retval = 0;
|
||||
return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
|
||||
}
|
||||
|
||||
if (aCount > maxCount) {
|
||||
aCount = maxCount;
|
||||
}
|
||||
nsresult rv = aWriter(this, aClosure,
|
||||
reinterpret_cast<const char*>
|
||||
(mImageBuffer+mImageBufferReadPoint),
|
||||
0, aCount, _retval);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
NS_ASSERTION(*_retval <= aCount, "bad write count");
|
||||
mImageBufferReadPoint += *_retval;
|
||||
}
|
||||
|
||||
// errors returned from the writer end here!
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::IsNonBlocking(bool* _retval)
|
||||
{
|
||||
*_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::AsyncWait(nsIInputStreamCallback* aCallback,
|
||||
uint32_t aFlags, uint32_t aRequestedCount,
|
||||
nsIEventTarget* aTarget)
|
||||
{
|
||||
if (aFlags != 0) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
if (mCallback || mCallbackTarget) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
mCallbackTarget = aTarget;
|
||||
// 0 means "any number of bytes except 0"
|
||||
mNotifyThreshold = aRequestedCount;
|
||||
if (!aRequestedCount) {
|
||||
mNotifyThreshold = 1024; // 1 KB seems good. We don't want to
|
||||
// notify incessantly
|
||||
}
|
||||
|
||||
// We set the callback absolutely last, because NotifyListener uses it to
|
||||
// determine if someone needs to be notified. If we don't set it last,
|
||||
// NotifyListener might try to fire off a notification to a null target
|
||||
// which will generally cause non-threadsafe objects to be used off the
|
||||
// main thread
|
||||
mCallback = aCallback;
|
||||
|
||||
// What we are being asked for may be present already
|
||||
NotifyListener();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsJPEGEncoder::CloseWithStatus(nsresult aStatus)
|
||||
{
|
||||
return Close();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// nsJPEGEncoder::ConvertHostARGBRow
|
||||
//
|
||||
// Our colors are stored with premultiplied alphas, but we need
|
||||
// an output with no alpha in machine-independent byte order.
|
||||
//
|
||||
// See gfx/cairo/cairo/src/cairo-png.c
|
||||
void
|
||||
nsJPEGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest,
|
||||
uint32_t aPixelWidth)
|
||||
{
|
||||
for (uint32_t x = 0; x < aPixelWidth; x++) {
|
||||
const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x];
|
||||
uint8_t* pixelOut = &aDest[x * 3];
|
||||
|
||||
pixelOut[0] = (pixelIn & 0xff0000) >> 16;
|
||||
pixelOut[1] = (pixelIn & 0x00ff00) >> 8;
|
||||
pixelOut[2] = (pixelIn & 0x0000ff) >> 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* nsJPEGEncoder::ConvertRGBARow
|
||||
*
|
||||
* Input is RGBA, output is RGB, so we should alpha-premultiply.
|
||||
*/
|
||||
void
|
||||
nsJPEGEncoder::ConvertRGBARow(const uint8_t* aSrc, uint8_t* aDest,
|
||||
uint32_t aPixelWidth)
|
||||
{
|
||||
for (uint32_t x = 0; x < aPixelWidth; x++) {
|
||||
const uint8_t* pixelIn = &aSrc[x * 4];
|
||||
uint8_t* pixelOut = &aDest[x * 3];
|
||||
|
||||
uint8_t alpha = pixelIn[3];
|
||||
pixelOut[0] = gfxPreMultiply(pixelIn[0], alpha);
|
||||
pixelOut[1] = gfxPreMultiply(pixelIn[1], alpha);
|
||||
pixelOut[2] = gfxPreMultiply(pixelIn[2], alpha);
|
||||
}
|
||||
}
|
||||
|
||||
// nsJPEGEncoder::initDestination
|
||||
//
|
||||
// Initialize destination. This is called by jpeg_start_compress() before
|
||||
// any data is actually written. It must initialize next_output_byte and
|
||||
// free_in_buffer. free_in_buffer must be initialized to a positive value.
|
||||
|
||||
void // static
|
||||
nsJPEGEncoder::initDestination(jpeg_compress_struct* cinfo)
|
||||
{
|
||||
nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
|
||||
NS_ASSERTION(!that->mImageBuffer, "Image buffer already initialized");
|
||||
|
||||
that->mImageBufferSize = 8192;
|
||||
that->mImageBuffer = (uint8_t*)malloc(that->mImageBufferSize);
|
||||
that->mImageBufferUsed = 0;
|
||||
|
||||
cinfo->dest->next_output_byte = that->mImageBuffer;
|
||||
cinfo->dest->free_in_buffer = that->mImageBufferSize;
|
||||
}
|
||||
|
||||
|
||||
// nsJPEGEncoder::emptyOutputBuffer
|
||||
//
|
||||
// This is called whenever the buffer has filled (free_in_buffer reaches
|
||||
// zero). In typical applications, it should write out the *entire* buffer
|
||||
// (use the saved start address and buffer length; ignore the current state
|
||||
// of next_output_byte and free_in_buffer). Then reset the pointer & count
|
||||
// to the start of the buffer, and return TRUE indicating that the buffer
|
||||
// has been dumped. free_in_buffer must be set to a positive value when
|
||||
// TRUE is returned. A FALSE return should only be used when I/O suspension
|
||||
// is desired (this operating mode is discussed in the next section).
|
||||
|
||||
boolean // static
|
||||
nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo)
|
||||
{
|
||||
nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
|
||||
NS_ASSERTION(that->mImageBuffer, "No buffer to empty!");
|
||||
|
||||
// When we're reallocing the buffer we need to take the lock to ensure
|
||||
// that nobody is trying to read from the buffer we are destroying
|
||||
ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor);
|
||||
|
||||
that->mImageBufferUsed = that->mImageBufferSize;
|
||||
|
||||
// expand buffer, just double size each time
|
||||
that->mImageBufferSize *= 2;
|
||||
|
||||
uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer,
|
||||
that->mImageBufferSize);
|
||||
if (!newBuf) {
|
||||
// can't resize, just zero (this will keep us from writing more)
|
||||
free(that->mImageBuffer);
|
||||
that->mImageBuffer = nullptr;
|
||||
that->mImageBufferSize = 0;
|
||||
that->mImageBufferUsed = 0;
|
||||
|
||||
// This seems to be the only way to do errors through the JPEG library. We
|
||||
// pass an nsresult masquerading as an int, which works because the
|
||||
// setjmp() caller casts it back.
|
||||
longjmp(((encoder_error_mgr*)(cinfo->err))->setjmp_buffer,
|
||||
static_cast<int>(NS_ERROR_OUT_OF_MEMORY));
|
||||
}
|
||||
that->mImageBuffer = newBuf;
|
||||
|
||||
cinfo->dest->next_output_byte = &that->mImageBuffer[that->mImageBufferUsed];
|
||||
cinfo->dest->free_in_buffer = that->mImageBufferSize - that->mImageBufferUsed;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// nsJPEGEncoder::termDestination
|
||||
//
|
||||
// Terminate destination --- called by jpeg_finish_compress() after all data
|
||||
// has been written. In most applications, this must flush any data
|
||||
// remaining in the buffer. Use either next_output_byte or free_in_buffer
|
||||
// to determine how much data is in the buffer.
|
||||
|
||||
void // static
|
||||
nsJPEGEncoder::termDestination(jpeg_compress_struct* cinfo)
|
||||
{
|
||||
nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data);
|
||||
if (!that->mImageBuffer) {
|
||||
return;
|
||||
}
|
||||
that->mImageBufferUsed = cinfo->dest->next_output_byte - that->mImageBuffer;
|
||||
NS_ASSERTION(that->mImageBufferUsed < that->mImageBufferSize,
|
||||
"JPEG library busted, got a bad image buffer size");
|
||||
that->NotifyListener();
|
||||
}
|
||||
|
||||
|
||||
// nsJPEGEncoder::errorExit
|
||||
//
|
||||
// Override the standard error method in the IJG JPEG decoder code. This
|
||||
// was mostly copied from nsJPEGDecoder.cpp
|
||||
|
||||
void // static
|
||||
nsJPEGEncoder::errorExit(jpeg_common_struct* cinfo)
|
||||
{
|
||||
nsresult error_code;
|
||||
encoder_error_mgr* err = (encoder_error_mgr*) cinfo->err;
|
||||
|
||||
// Convert error to a browser error code
|
||||
switch (cinfo->err->msg_code) {
|
||||
case JERR_OUT_OF_MEMORY:
|
||||
error_code = NS_ERROR_OUT_OF_MEMORY;
|
||||
break;
|
||||
default:
|
||||
error_code = NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Return control to the setjmp point. We pass an nsresult masquerading as
|
||||
// an int, which works because the setjmp() caller casts it back.
|
||||
longjmp(err->setjmp_buffer, static_cast<int>(error_code));
|
||||
}
|
||||
|
||||
void
|
||||
nsJPEGEncoder::NotifyListener()
|
||||
{
|
||||
// We might call this function on multiple threads (any threads that call
|
||||
// AsyncWait and any that do encoding) so we lock to avoid notifying the
|
||||
// listener twice about the same data (which generally leads to a truncated
|
||||
// image).
|
||||
ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
|
||||
|
||||
if (mCallback &&
|
||||
(mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold ||
|
||||
mFinished)) {
|
||||
nsCOMPtr<nsIInputStreamCallback> callback;
|
||||
if (mCallbackTarget) {
|
||||
callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
|
||||
} else {
|
||||
callback = mCallback;
|
||||
}
|
||||
|
||||
NS_ASSERTION(callback, "Shouldn't fail to make the callback");
|
||||
// Null the callback first because OnInputStreamReady could reenter
|
||||
// AsyncWait
|
||||
mCallback = nullptr;
|
||||
mCallbackTarget = nullptr;
|
||||
mNotifyThreshold = 0;
|
||||
|
||||
callback->OnInputStreamReady(this);
|
||||
}
|
||||
}
|
||||
84
image/encoders/jpeg/nsJPEGEncoder.h
Normal file
84
image/encoders/jpeg/nsJPEGEncoder.h
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef mozilla_image_encoders_jpeg_nsJPEGEncoder_h
|
||||
#define mozilla_image_encoders_jpeg_nsJPEGEncoder_h
|
||||
|
||||
#include "imgIEncoder.h"
|
||||
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
// needed for JPEG library
|
||||
#include <stdio.h>
|
||||
|
||||
extern "C" {
|
||||
#include "jpeglib.h"
|
||||
}
|
||||
|
||||
#define NS_JPEGENCODER_CID \
|
||||
{ \
|
||||
/* ac2bb8fe-eeeb-4572-b40f-be03932b56e0 */ \
|
||||
0xac2bb8fe, \
|
||||
0xeeeb, \
|
||||
0x4572, \
|
||||
{0xb4, 0x0f, 0xbe, 0x03, 0x93, 0x2b, 0x56, 0xe0} \
|
||||
}
|
||||
|
||||
// Provides JPEG encoding functionality. Use InitFromData() to do the
|
||||
// encoding. See that function definition for encoding options.
|
||||
|
||||
class nsJPEGEncoder final : public imgIEncoder
|
||||
{
|
||||
typedef mozilla::ReentrantMonitor ReentrantMonitor;
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_IMGIENCODER
|
||||
NS_DECL_NSIINPUTSTREAM
|
||||
NS_DECL_NSIASYNCINPUTSTREAM
|
||||
|
||||
nsJPEGEncoder();
|
||||
|
||||
private:
|
||||
~nsJPEGEncoder();
|
||||
|
||||
protected:
|
||||
|
||||
void ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest,
|
||||
uint32_t aPixelWidth);
|
||||
void ConvertRGBARow(const uint8_t* aSrc, uint8_t* aDest,
|
||||
uint32_t aPixelWidth);
|
||||
|
||||
static void initDestination(jpeg_compress_struct* cinfo);
|
||||
static boolean emptyOutputBuffer(jpeg_compress_struct* cinfo);
|
||||
static void termDestination(jpeg_compress_struct* cinfo);
|
||||
|
||||
static void errorExit(jpeg_common_struct* cinfo);
|
||||
|
||||
void NotifyListener();
|
||||
|
||||
bool mFinished;
|
||||
|
||||
// image buffer
|
||||
uint8_t* mImageBuffer;
|
||||
uint32_t mImageBufferSize;
|
||||
uint32_t mImageBufferUsed;
|
||||
|
||||
uint32_t mImageBufferReadPoint;
|
||||
|
||||
nsCOMPtr<nsIInputStreamCallback> mCallback;
|
||||
nsCOMPtr<nsIEventTarget> mCallbackTarget;
|
||||
uint32_t mNotifyThreshold;
|
||||
|
||||
// nsJPEGEncoder is designed to allow one thread to pump data into it while
|
||||
// another reads from it. We lock to ensure that the buffer remains
|
||||
// append-only while we read from it (that it is not realloced) and to ensure
|
||||
// that only one thread dispatches a callback for each call to AsyncWait.
|
||||
ReentrantMonitor mReentrantMonitor;
|
||||
};
|
||||
|
||||
#endif // mozilla_image_encoders_jpeg_nsJPEGEncoder_h
|
||||
12
image/encoders/moz.build
Normal file
12
image/encoders/moz.build
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'ico',
|
||||
'png',
|
||||
'jpeg',
|
||||
'bmp',
|
||||
]
|
||||
15
image/encoders/png/moz.build
Normal file
15
image/encoders/png/moz.build
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
SOURCES += [
|
||||
'nsPNGEncoder.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'/image',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
758
image/encoders/png/nsPNGEncoder.cpp
Normal file
758
image/encoders/png/nsPNGEncoder.cpp
Normal file
|
|
@ -0,0 +1,758 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#include "ImageLogging.h"
|
||||
#include "nsCRT.h"
|
||||
#include "nsPNGEncoder.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsString.h"
|
||||
#include "prprf.h"
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
static LazyLogModule sPNGEncoderLog("PNGEncoder");
|
||||
|
||||
NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream,
|
||||
nsIAsyncInputStream)
|
||||
|
||||
nsPNGEncoder::nsPNGEncoder() : mPNG(nullptr), mPNGinfo(nullptr),
|
||||
mIsAnimation(false),
|
||||
mFinished(false),
|
||||
mImageBuffer(nullptr), mImageBufferSize(0),
|
||||
mImageBufferUsed(0), mImageBufferReadPoint(0),
|
||||
mCallback(nullptr),
|
||||
mCallbackTarget(nullptr), mNotifyThreshold(0),
|
||||
mReentrantMonitor(
|
||||
"nsPNGEncoder.mReentrantMonitor")
|
||||
{ }
|
||||
|
||||
nsPNGEncoder::~nsPNGEncoder()
|
||||
{
|
||||
if (mImageBuffer) {
|
||||
free(mImageBuffer);
|
||||
mImageBuffer = nullptr;
|
||||
}
|
||||
// don't leak if EndImageEncode wasn't called
|
||||
if (mPNG) {
|
||||
png_destroy_write_struct(&mPNG, &mPNGinfo);
|
||||
}
|
||||
}
|
||||
|
||||
// nsPNGEncoder::InitFromData
|
||||
//
|
||||
// One output option is supported: "transparency=none" means that the
|
||||
// output PNG will not have an alpha channel, even if the input does.
|
||||
//
|
||||
// Based partially on gfx/cairo/cairo/src/cairo-png.c
|
||||
// See also media/libpng/libpng-manual.txt
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::InitFromData(const uint8_t* aData,
|
||||
uint32_t aLength, // (unused, req'd by JS)
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aStride,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
NS_ENSURE_ARG(aData);
|
||||
nsresult rv;
|
||||
|
||||
rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions);
|
||||
if (!NS_SUCCEEDED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride,
|
||||
aInputFormat, aOutputOptions);
|
||||
if (!NS_SUCCEEDED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = EndImageEncode();
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
|
||||
// nsPNGEncoder::StartImageEncode
|
||||
//
|
||||
//
|
||||
// See ::InitFromData for other info.
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::StartImageEncode(uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
bool useTransparency = true, skipFirstFrame = false;
|
||||
uint32_t numFrames = 1;
|
||||
uint32_t numPlays = 0; // For animations, 0 == forever
|
||||
|
||||
// can't initialize more than once
|
||||
if (mImageBuffer != nullptr) {
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
}
|
||||
|
||||
// validate input format
|
||||
if (aInputFormat != INPUT_FORMAT_RGB &&
|
||||
aInputFormat != INPUT_FORMAT_RGBA &&
|
||||
aInputFormat != INPUT_FORMAT_HOSTARGB)
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
||||
// parse and check any provided output options
|
||||
nsresult rv = ParseOptions(aOutputOptions, &useTransparency, &skipFirstFrame,
|
||||
&numFrames, &numPlays, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr);
|
||||
if (rv != NS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
#ifdef PNG_APNG_SUPPORTED
|
||||
if (numFrames > 1) {
|
||||
mIsAnimation = true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// initialize
|
||||
mPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
||||
nullptr,
|
||||
ErrorCallback,
|
||||
WarningCallback);
|
||||
if (!mPNG) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
mPNGinfo = png_create_info_struct(mPNG);
|
||||
if (!mPNGinfo) {
|
||||
png_destroy_write_struct(&mPNG, nullptr);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// libpng's error handler jumps back here upon an error.
|
||||
// Note: It's important that all png_* callers do this, or errors
|
||||
// will result in a corrupt time-warped stack.
|
||||
if (setjmp(png_jmpbuf(mPNG))) {
|
||||
png_destroy_write_struct(&mPNG, &mPNGinfo);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Set up to read the data into our image buffer, start out with an 8K
|
||||
// estimated size. Note: we don't have to worry about freeing this data
|
||||
// in this function. It will be freed on object destruction.
|
||||
mImageBufferSize = 8192;
|
||||
mImageBuffer = (uint8_t*)malloc(mImageBufferSize);
|
||||
if (!mImageBuffer) {
|
||||
png_destroy_write_struct(&mPNG, &mPNGinfo);
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
mImageBufferUsed = 0;
|
||||
|
||||
// set our callback for libpng to give us the data
|
||||
png_set_write_fn(mPNG, this, WriteCallback, nullptr);
|
||||
|
||||
// include alpha?
|
||||
int colorType;
|
||||
if ((aInputFormat == INPUT_FORMAT_HOSTARGB ||
|
||||
aInputFormat == INPUT_FORMAT_RGBA) &&
|
||||
useTransparency)
|
||||
colorType = PNG_COLOR_TYPE_RGB_ALPHA;
|
||||
else
|
||||
colorType = PNG_COLOR_TYPE_RGB;
|
||||
|
||||
png_set_IHDR(mPNG, mPNGinfo, aWidth, aHeight, 8, colorType,
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
#ifdef PNG_APNG_SUPPORTED
|
||||
if (mIsAnimation) {
|
||||
png_set_first_frame_is_hidden(mPNG, mPNGinfo, skipFirstFrame);
|
||||
png_set_acTL(mPNG, mPNGinfo, numFrames, numPlays);
|
||||
}
|
||||
#endif
|
||||
|
||||
// XXX: support PLTE, gAMA, tRNS, bKGD?
|
||||
|
||||
png_write_info(mPNG, mPNGinfo);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Returns the number of bytes in the image buffer used.
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::GetImageBufferUsed(uint32_t* aOutputSize)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aOutputSize);
|
||||
*aOutputSize = mImageBufferUsed;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Returns a pointer to the start of the image buffer
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::GetImageBuffer(char** aOutputBuffer)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aOutputBuffer);
|
||||
*aOutputBuffer = reinterpret_cast<char*>(mImageBuffer);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::AddImageFrame(const uint8_t* aData,
|
||||
uint32_t aLength, // (unused, req'd by JS)
|
||||
uint32_t aWidth,
|
||||
uint32_t aHeight,
|
||||
uint32_t aStride,
|
||||
uint32_t aInputFormat,
|
||||
const nsAString& aFrameOptions)
|
||||
{
|
||||
bool useTransparency= true;
|
||||
uint32_t delay_ms = 500;
|
||||
#ifdef PNG_APNG_SUPPORTED
|
||||
uint32_t dispose_op = PNG_DISPOSE_OP_NONE;
|
||||
uint32_t blend_op = PNG_BLEND_OP_SOURCE;
|
||||
#else
|
||||
uint32_t dispose_op;
|
||||
uint32_t blend_op;
|
||||
#endif
|
||||
uint32_t x_offset = 0, y_offset = 0;
|
||||
|
||||
// must be initialized
|
||||
if (mImageBuffer == nullptr) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// EndImageEncode was done, or some error occurred earlier
|
||||
if (!mPNG) {
|
||||
return NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
// validate input format
|
||||
if (aInputFormat != INPUT_FORMAT_RGB &&
|
||||
aInputFormat != INPUT_FORMAT_RGBA &&
|
||||
aInputFormat != INPUT_FORMAT_HOSTARGB)
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
||||
// libpng's error handler jumps back here upon an error.
|
||||
if (setjmp(png_jmpbuf(mPNG))) {
|
||||
png_destroy_write_struct(&mPNG, &mPNGinfo);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// parse and check any provided output options
|
||||
nsresult rv = ParseOptions(aFrameOptions, &useTransparency, nullptr,
|
||||
nullptr, nullptr, &dispose_op, &blend_op,
|
||||
&delay_ms, &x_offset, &y_offset);
|
||||
if (rv != NS_OK) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
#ifdef PNG_APNG_SUPPORTED
|
||||
if (mIsAnimation) {
|
||||
// XXX the row pointers arg (#3) is unused, can it be removed?
|
||||
png_write_frame_head(mPNG, mPNGinfo, nullptr,
|
||||
aWidth, aHeight, x_offset, y_offset,
|
||||
delay_ms, 1000, dispose_op, blend_op);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Stride is the padded width of each row, so it better be longer
|
||||
// (I'm afraid people will not understand what stride means, so
|
||||
// check it well)
|
||||
if ((aInputFormat == INPUT_FORMAT_RGB &&
|
||||
aStride < aWidth * 3) ||
|
||||
((aInputFormat == INPUT_FORMAT_RGBA ||
|
||||
aInputFormat == INPUT_FORMAT_HOSTARGB) &&
|
||||
aStride < aWidth * 4)) {
|
||||
NS_WARNING("Invalid stride for InitFromData/AddImageFrame");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
#ifdef PNG_WRITE_FILTER_SUPPORTED
|
||||
png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE);
|
||||
#endif
|
||||
|
||||
// write each row: if we add more input formats, we may want to
|
||||
// generalize the conversions
|
||||
if (aInputFormat == INPUT_FORMAT_HOSTARGB) {
|
||||
// PNG requires RGBA with post-multiplied alpha, so we need to
|
||||
// convert
|
||||
UniquePtr<uint8_t[]> row = MakeUnique<uint8_t[]>(aWidth * 4);
|
||||
for (uint32_t y = 0; y < aHeight; y++) {
|
||||
ConvertHostARGBRow(&aData[y * aStride], row.get(), aWidth, useTransparency);
|
||||
png_write_row(mPNG, row.get());
|
||||
}
|
||||
} else if (aInputFormat == INPUT_FORMAT_RGBA && !useTransparency) {
|
||||
// RBGA, but we need to strip the alpha
|
||||
UniquePtr<uint8_t[]> row = MakeUnique<uint8_t[]>(aWidth * 4);
|
||||
for (uint32_t y = 0; y < aHeight; y++) {
|
||||
StripAlpha(&aData[y * aStride], row.get(), aWidth);
|
||||
png_write_row(mPNG, row.get());
|
||||
}
|
||||
} else if (aInputFormat == INPUT_FORMAT_RGB ||
|
||||
aInputFormat == INPUT_FORMAT_RGBA) {
|
||||
// simple RBG(A), no conversion needed
|
||||
for (uint32_t y = 0; y < aHeight; y++) {
|
||||
png_write_row(mPNG, (uint8_t*)&aData[y * aStride]);
|
||||
}
|
||||
|
||||
} else {
|
||||
NS_NOTREACHED("Bad format type");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
#ifdef PNG_APNG_SUPPORTED
|
||||
if (mIsAnimation) {
|
||||
png_write_frame_tail(mPNG, mPNGinfo);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::EndImageEncode()
|
||||
{
|
||||
// must be initialized
|
||||
if (mImageBuffer == nullptr) {
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
// EndImageEncode has already been called, or some error
|
||||
// occurred earlier
|
||||
if (!mPNG) {
|
||||
return NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
// libpng's error handler jumps back here upon an error.
|
||||
if (setjmp(png_jmpbuf(mPNG))) {
|
||||
png_destroy_write_struct(&mPNG, &mPNGinfo);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
png_write_end(mPNG, mPNGinfo);
|
||||
png_destroy_write_struct(&mPNG, &mPNGinfo);
|
||||
|
||||
mFinished = true;
|
||||
NotifyListener();
|
||||
|
||||
// if output callback can't get enough memory, it will free our buffer
|
||||
if (!mImageBuffer) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
nsPNGEncoder::ParseOptions(const nsAString& aOptions,
|
||||
bool* useTransparency,
|
||||
bool* skipFirstFrame,
|
||||
uint32_t* numFrames,
|
||||
uint32_t* numPlays,
|
||||
uint32_t* frameDispose,
|
||||
uint32_t* frameBlend,
|
||||
uint32_t* frameDelay,
|
||||
uint32_t* offsetX,
|
||||
uint32_t* offsetY)
|
||||
{
|
||||
#ifdef PNG_APNG_SUPPORTED
|
||||
// Make a copy of aOptions, because strtok() will modify it.
|
||||
nsAutoCString optionsCopy;
|
||||
optionsCopy.Assign(NS_ConvertUTF16toUTF8(aOptions));
|
||||
char* options = optionsCopy.BeginWriting();
|
||||
|
||||
while (char* token = nsCRT::strtok(options, ";", &options)) {
|
||||
// If there's an '=' character, split the token around it.
|
||||
char* equals = token;
|
||||
char* value = nullptr;
|
||||
while(*equals != '=' && *equals) {
|
||||
++equals;
|
||||
}
|
||||
if (*equals == '=') {
|
||||
value = equals + 1;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
*equals = '\0'; // temporary null
|
||||
}
|
||||
|
||||
// transparency=[yes|no|none]
|
||||
if (nsCRT::strcmp(token, "transparency") == 0 && useTransparency) {
|
||||
if (!value) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (nsCRT::strcmp(value, "none") == 0 ||
|
||||
nsCRT::strcmp(value, "no") == 0) {
|
||||
*useTransparency = false;
|
||||
} else if (nsCRT::strcmp(value, "yes") == 0) {
|
||||
*useTransparency = true;
|
||||
} else {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// skipfirstframe=[yes|no]
|
||||
} else if (nsCRT::strcmp(token, "skipfirstframe") == 0 &&
|
||||
skipFirstFrame) {
|
||||
if (!value) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (nsCRT::strcmp(value, "no") == 0) {
|
||||
*skipFirstFrame = false;
|
||||
} else if (nsCRT::strcmp(value, "yes") == 0) {
|
||||
*skipFirstFrame = true;
|
||||
} else {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// frames=#
|
||||
} else if (nsCRT::strcmp(token, "frames") == 0 && numFrames) {
|
||||
if (!value) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (PR_sscanf(value, "%u", numFrames) != 1) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// frames=0 is nonsense.
|
||||
if (*numFrames == 0) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// plays=#
|
||||
} else if (nsCRT::strcmp(token, "plays") == 0 && numPlays) {
|
||||
if (!value) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// plays=0 to loop forever, otherwise play sequence specified
|
||||
// number of times
|
||||
if (PR_sscanf(value, "%u", numPlays) != 1) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// dispose=[none|background|previous]
|
||||
} else if (nsCRT::strcmp(token, "dispose") == 0 && frameDispose) {
|
||||
if (!value) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (nsCRT::strcmp(value, "none") == 0) {
|
||||
*frameDispose = PNG_DISPOSE_OP_NONE;
|
||||
} else if (nsCRT::strcmp(value, "background") == 0) {
|
||||
*frameDispose = PNG_DISPOSE_OP_BACKGROUND;
|
||||
} else if (nsCRT::strcmp(value, "previous") == 0) {
|
||||
*frameDispose = PNG_DISPOSE_OP_PREVIOUS;
|
||||
} else {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// blend=[source|over]
|
||||
} else if (nsCRT::strcmp(token, "blend") == 0 && frameBlend) {
|
||||
if (!value) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (nsCRT::strcmp(value, "source") == 0) {
|
||||
*frameBlend = PNG_BLEND_OP_SOURCE;
|
||||
} else if (nsCRT::strcmp(value, "over") == 0) {
|
||||
*frameBlend = PNG_BLEND_OP_OVER;
|
||||
} else {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// delay=# (in ms)
|
||||
} else if (nsCRT::strcmp(token, "delay") == 0 && frameDelay) {
|
||||
if (!value) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (PR_sscanf(value, "%u", frameDelay) != 1) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// xoffset=#
|
||||
} else if (nsCRT::strcmp(token, "xoffset") == 0 && offsetX) {
|
||||
if (!value) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (PR_sscanf(value, "%u", offsetX) != 1) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// yoffset=#
|
||||
} else if (nsCRT::strcmp(token, "yoffset") == 0 && offsetY) {
|
||||
if (!value) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (PR_sscanf(value, "%u", offsetY) != 1) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// unknown token name
|
||||
} else
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
||||
if (value) {
|
||||
*equals = '='; // restore '=' so strtok doesn't get lost
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::Close()
|
||||
{
|
||||
if (mImageBuffer != nullptr) {
|
||||
free(mImageBuffer);
|
||||
mImageBuffer = nullptr;
|
||||
mImageBufferSize = 0;
|
||||
mImageBufferUsed = 0;
|
||||
mImageBufferReadPoint = 0;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::Available(uint64_t* _retval)
|
||||
{
|
||||
if (!mImageBuffer) {
|
||||
return NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
*_retval = mImageBufferUsed - mImageBufferReadPoint;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
|
||||
{
|
||||
return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter,
|
||||
void* aClosure, uint32_t aCount,
|
||||
uint32_t* _retval)
|
||||
{
|
||||
// Avoid another thread reallocing the buffer underneath us
|
||||
ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
|
||||
|
||||
uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint;
|
||||
if (maxCount == 0) {
|
||||
*_retval = 0;
|
||||
return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK;
|
||||
}
|
||||
|
||||
if (aCount > maxCount) {
|
||||
aCount = maxCount;
|
||||
}
|
||||
|
||||
nsresult rv =
|
||||
aWriter(this, aClosure,
|
||||
reinterpret_cast<const char*>(mImageBuffer+mImageBufferReadPoint),
|
||||
0, aCount, _retval);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
NS_ASSERTION(*_retval <= aCount, "bad write count");
|
||||
mImageBufferReadPoint += *_retval;
|
||||
}
|
||||
|
||||
// errors returned from the writer end here!
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::IsNonBlocking(bool* _retval)
|
||||
{
|
||||
*_retval = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::AsyncWait(nsIInputStreamCallback* aCallback,
|
||||
uint32_t aFlags,
|
||||
uint32_t aRequestedCount,
|
||||
nsIEventTarget* aTarget)
|
||||
{
|
||||
if (aFlags != 0) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
if (mCallback || mCallbackTarget) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
mCallbackTarget = aTarget;
|
||||
// 0 means "any number of bytes except 0"
|
||||
mNotifyThreshold = aRequestedCount;
|
||||
if (!aRequestedCount) {
|
||||
mNotifyThreshold = 1024; // We don't want to notify incessantly
|
||||
}
|
||||
|
||||
// We set the callback absolutely last, because NotifyListener uses it to
|
||||
// determine if someone needs to be notified. If we don't set it last,
|
||||
// NotifyListener might try to fire off a notification to a null target
|
||||
// which will generally cause non-threadsafe objects to be used off the main
|
||||
// thread
|
||||
mCallback = aCallback;
|
||||
|
||||
// What we are being asked for may be present already
|
||||
NotifyListener();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsPNGEncoder::CloseWithStatus(nsresult aStatus)
|
||||
{
|
||||
return Close();
|
||||
}
|
||||
|
||||
// nsPNGEncoder::ConvertHostARGBRow
|
||||
//
|
||||
// Our colors are stored with premultiplied alphas, but PNGs use
|
||||
// post-multiplied alpha. This swaps to PNG-style alpha.
|
||||
//
|
||||
// Copied from gfx/cairo/cairo/src/cairo-png.c
|
||||
|
||||
void
|
||||
nsPNGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest,
|
||||
uint32_t aPixelWidth,
|
||||
bool aUseTransparency)
|
||||
{
|
||||
uint32_t pixelStride = aUseTransparency ? 4 : 3;
|
||||
for (uint32_t x = 0; x < aPixelWidth; x++) {
|
||||
const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x];
|
||||
uint8_t* pixelOut = &aDest[x * pixelStride];
|
||||
|
||||
uint8_t alpha = (pixelIn & 0xff000000) >> 24;
|
||||
pixelOut[pixelStride - 1] = alpha; // overwritten below if pixelStride == 3
|
||||
if (alpha == 255) {
|
||||
pixelOut[0] = (pixelIn & 0xff0000) >> 16;
|
||||
pixelOut[1] = (pixelIn & 0x00ff00) >> 8;
|
||||
pixelOut[2] = (pixelIn & 0x0000ff) ;
|
||||
} else if (alpha == 0) {
|
||||
pixelOut[0] = pixelOut[1] = pixelOut[2] = 0;
|
||||
} else {
|
||||
pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
|
||||
pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
|
||||
pixelOut[2] = (((pixelIn & 0x0000ff) ) * 255 + alpha / 2) / alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// nsPNGEncoder::StripAlpha
|
||||
//
|
||||
// Input is RGBA, output is RGB
|
||||
|
||||
void
|
||||
nsPNGEncoder::StripAlpha(const uint8_t* aSrc, uint8_t* aDest,
|
||||
uint32_t aPixelWidth)
|
||||
{
|
||||
for (uint32_t x = 0; x < aPixelWidth; x++) {
|
||||
const uint8_t* pixelIn = &aSrc[x * 4];
|
||||
uint8_t* pixelOut = &aDest[x * 3];
|
||||
pixelOut[0] = pixelIn[0];
|
||||
pixelOut[1] = pixelIn[1];
|
||||
pixelOut[2] = pixelIn[2];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// nsPNGEncoder::WarningCallback
|
||||
|
||||
void
|
||||
nsPNGEncoder::WarningCallback(png_structp png_ptr,
|
||||
png_const_charp warning_msg)
|
||||
{
|
||||
MOZ_LOG(sPNGEncoderLog, LogLevel::Warning,
|
||||
("libpng warning: %s\n", warning_msg));
|
||||
}
|
||||
|
||||
|
||||
// nsPNGEncoder::ErrorCallback
|
||||
|
||||
void
|
||||
nsPNGEncoder::ErrorCallback(png_structp png_ptr,
|
||||
png_const_charp error_msg)
|
||||
{
|
||||
MOZ_LOG(sPNGEncoderLog, LogLevel::Error, ("libpng error: %s\n", error_msg));
|
||||
png_longjmp(png_ptr, 1);
|
||||
}
|
||||
|
||||
// nsPNGEncoder::WriteCallback
|
||||
|
||||
void // static
|
||||
nsPNGEncoder::WriteCallback(png_structp png, png_bytep data,
|
||||
png_size_t size)
|
||||
{
|
||||
nsPNGEncoder* that = static_cast<nsPNGEncoder*>(png_get_io_ptr(png));
|
||||
if (!that->mImageBuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (that->mImageBufferUsed + size > that->mImageBufferSize) {
|
||||
// When we're reallocing the buffer we need to take the lock to ensure
|
||||
// that nobody is trying to read from the buffer we are destroying
|
||||
ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor);
|
||||
|
||||
// expand buffer, just double each time
|
||||
that->mImageBufferSize *= 2;
|
||||
uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer,
|
||||
that->mImageBufferSize);
|
||||
if (!newBuf) {
|
||||
// can't resize, just zero (this will keep us from writing more)
|
||||
free(that->mImageBuffer);
|
||||
that->mImageBuffer = nullptr;
|
||||
that->mImageBufferSize = 0;
|
||||
that->mImageBufferUsed = 0;
|
||||
return;
|
||||
}
|
||||
that->mImageBuffer = newBuf;
|
||||
}
|
||||
memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size);
|
||||
that->mImageBufferUsed += size;
|
||||
that->NotifyListener();
|
||||
}
|
||||
|
||||
void
|
||||
nsPNGEncoder::NotifyListener()
|
||||
{
|
||||
// We might call this function on multiple threads (any threads that call
|
||||
// AsyncWait and any that do encoding) so we lock to avoid notifying the
|
||||
// listener twice about the same data (which generally leads to a truncated
|
||||
// image).
|
||||
ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor);
|
||||
|
||||
if (mCallback &&
|
||||
(mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold ||
|
||||
mFinished)) {
|
||||
nsCOMPtr<nsIInputStreamCallback> callback;
|
||||
if (mCallbackTarget) {
|
||||
callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget);
|
||||
} else {
|
||||
callback = mCallback;
|
||||
}
|
||||
|
||||
NS_ASSERTION(callback, "Shouldn't fail to make the callback");
|
||||
// Null the callback first because OnInputStreamReady could reenter
|
||||
// AsyncWait
|
||||
mCallback = nullptr;
|
||||
mCallbackTarget = nullptr;
|
||||
mNotifyThreshold = 0;
|
||||
|
||||
callback->OnInputStreamReady(this);
|
||||
}
|
||||
}
|
||||
83
image/encoders/png/nsPNGEncoder.h
Normal file
83
image/encoders/png/nsPNGEncoder.h
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/* -*- 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/. */
|
||||
|
||||
#ifndef nsPNGEncoder_h
|
||||
|
||||
#include <png.h>
|
||||
|
||||
#include "imgIEncoder.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/ReentrantMonitor.h"
|
||||
|
||||
#define NS_PNGENCODER_CID \
|
||||
{ /* 38d1592e-b81e-432b-86f8-471878bbfe07 */ \
|
||||
0x38d1592e, \
|
||||
0xb81e, \
|
||||
0x432b, \
|
||||
{0x86, 0xf8, 0x47, 0x18, 0x78, 0xbb, 0xfe, 0x07} \
|
||||
}
|
||||
|
||||
// Provides PNG encoding functionality. Use InitFromData() to do the
|
||||
// encoding. See that function definition for encoding options.
|
||||
|
||||
class nsPNGEncoder final : public imgIEncoder
|
||||
{
|
||||
typedef mozilla::ReentrantMonitor ReentrantMonitor;
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_IMGIENCODER
|
||||
NS_DECL_NSIINPUTSTREAM
|
||||
NS_DECL_NSIASYNCINPUTSTREAM
|
||||
|
||||
nsPNGEncoder();
|
||||
|
||||
protected:
|
||||
~nsPNGEncoder();
|
||||
nsresult ParseOptions(const nsAString& aOptions,
|
||||
bool* useTransparency,
|
||||
bool* skipFirstFrame,
|
||||
uint32_t* numAnimatedFrames,
|
||||
uint32_t* numIterations,
|
||||
uint32_t* frameDispose,
|
||||
uint32_t* frameBlend,
|
||||
uint32_t* frameDelay,
|
||||
uint32_t* offsetX,
|
||||
uint32_t* offsetY);
|
||||
void ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest,
|
||||
uint32_t aPixelWidth, bool aUseTransparency);
|
||||
void StripAlpha(const uint8_t* aSrc, uint8_t* aDest,
|
||||
uint32_t aPixelWidth);
|
||||
static void WarningCallback(png_structp png_ptr, png_const_charp warning_msg);
|
||||
static void ErrorCallback(png_structp png_ptr, png_const_charp error_msg);
|
||||
static void WriteCallback(png_structp png, png_bytep data, png_size_t size);
|
||||
void NotifyListener();
|
||||
|
||||
png_struct* mPNG;
|
||||
png_info* mPNGinfo;
|
||||
|
||||
bool mIsAnimation;
|
||||
bool mFinished;
|
||||
|
||||
// image buffer
|
||||
uint8_t* mImageBuffer;
|
||||
uint32_t mImageBufferSize;
|
||||
uint32_t mImageBufferUsed;
|
||||
|
||||
uint32_t mImageBufferReadPoint;
|
||||
|
||||
nsCOMPtr<nsIInputStreamCallback> mCallback;
|
||||
nsCOMPtr<nsIEventTarget> mCallbackTarget;
|
||||
uint32_t mNotifyThreshold;
|
||||
|
||||
// nsPNGEncoder is designed to allow one thread to pump data into it while
|
||||
// another reads from it. We lock to ensure that the buffer remains
|
||||
// append-only while we read from it (that it is not realloced) and to
|
||||
// ensure that only one thread dispatches a callback for each call to
|
||||
// AsyncWait.
|
||||
ReentrantMonitor mReentrantMonitor;
|
||||
};
|
||||
#endif // nsPNGEncoder_h
|
||||
Loading…
Add table
Add a link
Reference in a new issue