import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo

This commit is contained in:
Roy Tam 2018-01-19 03:59:58 +08:00
commit dcd9973243
150858 changed files with 23884658 additions and 0 deletions

View 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'

View 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;
}
}

View 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

View 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'

View 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);
}

View 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

View 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'

View 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);
}
}

View 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
View 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',
]

View 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'

View 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);
}
}

View 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