зеркало из https://github.com/mozilla/pjs.git
Bug 375741 ��� Add support for APNG encoding, patch by Justin Dolske <dolske@mozilla.com>, r=asmith15, sr=pavlov
This commit is contained in:
Родитель
b78e0bd9ab
Коммит
b783c2ede6
|
@ -195,6 +195,31 @@ NS_IMETHODIMP nsJPEGEncoder::InitFromData(const PRUint8* aData,
|
|||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP nsJPEGEncoder::StartImageEncode(PRUint32 aWidth,
|
||||
PRUint32 aHeight,
|
||||
PRUint32 aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsJPEGEncoder::AddImageFrame(const PRUint8* aData,
|
||||
PRUint32 aLength,
|
||||
PRUint32 aWidth,
|
||||
PRUint32 aHeight,
|
||||
PRUint32 aStride,
|
||||
PRUint32 aFrameFormat,
|
||||
const nsAString& aFrameOptions)
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsJPEGEncoder::EndImageEncode()
|
||||
{
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
|
||||
/* void close (); */
|
||||
NS_IMETHODIMP nsJPEGEncoder::Close()
|
||||
{
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
* Contributor(s):
|
||||
* Brett Wilson <brettw@gmail.com>
|
||||
* Stuart Parmenter <pavlov@pavlov.net>
|
||||
* Justin Dolske <dolske@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
|
@ -37,8 +38,10 @@
|
|||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
#include "nsCRT.h"
|
||||
#include "nsPNGEncoder.h"
|
||||
#include "prmem.h"
|
||||
#include "prprf.h"
|
||||
#include "nsString.h"
|
||||
#include "nsStreamUtils.h"
|
||||
|
||||
|
@ -53,8 +56,10 @@
|
|||
// which read such a stream on a background thread.
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS2(nsPNGEncoder, imgIEncoder, nsIInputStream)
|
||||
|
||||
nsPNGEncoder::nsPNGEncoder() : mImageBuffer(nsnull), mImageBufferSize(0),
|
||||
mImageBufferUsed(0), mImageBufferReadPoint(0)
|
||||
nsPNGEncoder::nsPNGEncoder() : mPNG(nsnull), mPNGinfo(nsnull),
|
||||
mIsAnimation(PR_FALSE),
|
||||
mImageBuffer(nsnull), mImageBufferSize(0),
|
||||
mImageBufferUsed(0), mImageBufferReadPoint(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -82,49 +87,75 @@ NS_IMETHODIMP nsPNGEncoder::InitFromData(const PRUint8* aData,
|
|||
PRUint32 aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
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(PRUint32 aWidth,
|
||||
PRUint32 aHeight,
|
||||
PRUint32 aInputFormat,
|
||||
const nsAString& aOutputOptions)
|
||||
{
|
||||
PRBool useTransparency = PR_TRUE, skipFirstFrame = PR_FALSE;
|
||||
PRUint32 numFrames = 1;
|
||||
PRUint32 numPlays = 0; // For animations, 0 == forever
|
||||
|
||||
// can't initialize more than once
|
||||
if (mImageBuffer != nsnull)
|
||||
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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
// parse and check any provided output options
|
||||
nsresult rv = ParseOptions(aOutputOptions, &useTransparency, &skipFirstFrame,
|
||||
&numFrames, &numPlays, nsnull, nsnull,
|
||||
nsnull, nsnull, nsnull);
|
||||
if (rv != NS_OK) { return rv; }
|
||||
|
||||
// can't initialize more than once
|
||||
if (mImageBuffer != nsnull)
|
||||
return NS_ERROR_ALREADY_INITIALIZED;
|
||||
|
||||
// options: we only have one option so this is easy
|
||||
PRBool useTransparency = PR_TRUE;
|
||||
if (aOutputOptions.Length() >= 17) {
|
||||
if (StringBeginsWith(aOutputOptions, NS_LITERAL_STRING("transparency=none")))
|
||||
useTransparency = PR_FALSE;
|
||||
if (numFrames > 1) {
|
||||
mIsAnimation = PR_TRUE;
|
||||
}
|
||||
|
||||
// initialize
|
||||
png_struct* png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
||||
png_voidp_NULL,
|
||||
png_error_ptr_NULL,
|
||||
png_error_ptr_NULL);
|
||||
if (! png_ptr)
|
||||
mPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING,
|
||||
png_voidp_NULL,
|
||||
ErrorCallback,
|
||||
ErrorCallback);
|
||||
if (! mPNG)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
png_info* info_ptr = png_create_info_struct(png_ptr);
|
||||
if (! info_ptr)
|
||||
{
|
||||
png_destroy_write_struct(&png_ptr, nsnull);
|
||||
|
||||
mPNGinfo = png_create_info_struct(mPNG);
|
||||
if (! mPNGinfo) {
|
||||
png_destroy_write_struct(&mPNG, nsnull);
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
// 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
|
||||
|
@ -133,13 +164,13 @@ NS_IMETHODIMP nsPNGEncoder::InitFromData(const PRUint8* aData,
|
|||
mImageBufferSize = 8192;
|
||||
mImageBuffer = (PRUint8*)PR_Malloc(mImageBufferSize);
|
||||
if (!mImageBuffer) {
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
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(png_ptr, this, WriteCallback, NULL);
|
||||
png_set_write_fn(mPNG, this, WriteCallback, NULL);
|
||||
|
||||
// include alpha?
|
||||
int colorType;
|
||||
|
@ -149,11 +180,75 @@ NS_IMETHODIMP nsPNGEncoder::InitFromData(const PRUint8* aData,
|
|||
else
|
||||
colorType = PNG_COLOR_TYPE_RGB;
|
||||
|
||||
png_set_IHDR(png_ptr, info_ptr, aWidth, aHeight, 8, colorType,
|
||||
png_set_IHDR(mPNG, mPNGinfo, aWidth, aHeight, 8, colorType,
|
||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
|
||||
PNG_FILTER_TYPE_DEFAULT);
|
||||
|
||||
png_write_info(png_ptr, info_ptr);
|
||||
if (mIsAnimation) {
|
||||
png_set_first_frame_is_hidden(mPNG, mPNGinfo, skipFirstFrame);
|
||||
png_set_acTL(mPNG, mPNGinfo, numFrames, numPlays);
|
||||
}
|
||||
|
||||
// XXX: support PLTE, gAMA, tRNS, bKGD?
|
||||
|
||||
png_write_info(mPNG, mPNGinfo);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP nsPNGEncoder::AddImageFrame(const PRUint8* aData,
|
||||
PRUint32 aLength, // (unused, req'd by JS)
|
||||
PRUint32 aWidth,
|
||||
PRUint32 aHeight,
|
||||
PRUint32 aStride,
|
||||
PRUint32 aInputFormat,
|
||||
const nsAString& aFrameOptions)
|
||||
{
|
||||
PRBool useTransparency= PR_TRUE;
|
||||
PRUint32 delay_ms = 500;
|
||||
PRUint32 dispose_op = PNG_DISPOSE_OP_NONE;
|
||||
PRUint32 blend_op = PNG_BLEND_OP_SOURCE;
|
||||
PRUint32 x_offset = 0, y_offset = 0;
|
||||
|
||||
// must be initialized
|
||||
if (mImageBuffer == nsnull)
|
||||
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;
|
||||
|
||||
// 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, nsnull,
|
||||
nsnull, nsnull, &dispose_op, &blend_op,
|
||||
&delay_ms, &x_offset, &y_offset);
|
||||
if (rv != NS_OK) { return rv; }
|
||||
|
||||
if (mIsAnimation) {
|
||||
// XXX the row pointers arg (#3) is unused, can it be removed?
|
||||
png_write_frame_head(mPNG, mPNGinfo, nsnull,
|
||||
aWidth, aHeight, x_offset, y_offset,
|
||||
delay_ms, 1000, dispose_op, blend_op);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// write each row: if we add more input formats, we may want to
|
||||
// generalize the conversions
|
||||
|
@ -162,7 +257,7 @@ NS_IMETHODIMP nsPNGEncoder::InitFromData(const PRUint8* aData,
|
|||
PRUint8* row = new PRUint8[aWidth * 4];
|
||||
for (PRUint32 y = 0; y < aHeight; y ++) {
|
||||
ConvertHostARGBRow(&aData[y * aStride], row, aWidth, useTransparency);
|
||||
png_write_row(png_ptr, row);
|
||||
png_write_row(mPNG, row);
|
||||
}
|
||||
delete[] row;
|
||||
|
||||
|
@ -171,7 +266,7 @@ NS_IMETHODIMP nsPNGEncoder::InitFromData(const PRUint8* aData,
|
|||
PRUint8* row = new PRUint8[aWidth * 4];
|
||||
for (PRUint32 y = 0; y < aHeight; y ++) {
|
||||
StripAlpha(&aData[y * aStride], row, aWidth);
|
||||
png_write_row(png_ptr, row);
|
||||
png_write_row(mPNG, row);
|
||||
}
|
||||
delete[] row;
|
||||
|
||||
|
@ -179,15 +274,36 @@ NS_IMETHODIMP nsPNGEncoder::InitFromData(const PRUint8* aData,
|
|||
aInputFormat == INPUT_FORMAT_RGBA) {
|
||||
// simple RBG(A), no conversion needed
|
||||
for (PRUint32 y = 0; y < aHeight; y ++) {
|
||||
png_write_row(png_ptr, (PRUint8*)&aData[y * aStride]);
|
||||
png_write_row(mPNG, (PRUint8*)&aData[y * aStride]);
|
||||
}
|
||||
|
||||
} else {
|
||||
NS_NOTREACHED("Bad format type");
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
png_write_end(png_ptr, info_ptr);
|
||||
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||
if (mIsAnimation) {
|
||||
png_write_frame_tail(mPNG, mPNGinfo);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP nsPNGEncoder::EndImageEncode()
|
||||
{
|
||||
// must be initialized
|
||||
if (mImageBuffer == nsnull)
|
||||
return NS_ERROR_NOT_INITIALIZED;
|
||||
|
||||
// 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);
|
||||
|
||||
// if output callback can't get enough memory, it will free our buffer
|
||||
if (!mImageBuffer)
|
||||
|
@ -197,6 +313,125 @@ NS_IMETHODIMP nsPNGEncoder::InitFromData(const PRUint8* aData,
|
|||
}
|
||||
|
||||
|
||||
nsresult
|
||||
nsPNGEncoder::ParseOptions(const nsAString& aOptions,
|
||||
PRBool* useTransparency,
|
||||
PRBool* skipFirstFrame,
|
||||
PRUint32* numFrames,
|
||||
PRUint32* numPlays,
|
||||
PRUint32* frameDispose,
|
||||
PRUint32* frameBlend,
|
||||
PRUint32* frameDelay,
|
||||
PRUint32* offsetX,
|
||||
PRUint32* offsetY)
|
||||
{
|
||||
char* token;
|
||||
char* options = nsCRT::strdup(PromiseFlatCString(NS_ConvertUTF16toUTF8(aOptions)).get());
|
||||
|
||||
while ((token = nsCRT::strtok(options, ";", &options))) {
|
||||
// If there's an '=' character, split the token around it.
|
||||
char* equals = token, *value = nsnull;
|
||||
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 = PR_FALSE;
|
||||
} else if (nsCRT::strcmp(value, "yes") == 0) {
|
||||
*useTransparency = PR_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 = PR_FALSE;
|
||||
} else if (nsCRT::strcmp(value, "yes") == 0) {
|
||||
*skipFirstFrame = PR_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
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
/* void close (); */
|
||||
NS_IMETHODIMP nsPNGEncoder::Close()
|
||||
{
|
||||
|
@ -308,6 +543,18 @@ nsPNGEncoder::StripAlpha(const PRUint8* aSrc, PRUint8* aDest,
|
|||
}
|
||||
|
||||
|
||||
// nsPNGEncoder::ErrorCallback
|
||||
|
||||
void // static
|
||||
nsPNGEncoder::ErrorCallback(png_structp png_ptr, png_const_charp warning_msg)
|
||||
{
|
||||
#ifdef DEBUG
|
||||
// XXX: these messages are probably useful callers... use nsIConsoleService?
|
||||
PR_fprintf(PR_STDERR, "PNG Encoder: %s\n", warning_msg);;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// nsPNGEncoder::WriteCallback
|
||||
|
||||
void // static
|
||||
|
|
|
@ -63,12 +63,28 @@ private:
|
|||
~nsPNGEncoder();
|
||||
|
||||
protected:
|
||||
nsresult ParseOptions(const nsAString& aOptions,
|
||||
PRBool* useTransparency,
|
||||
PRBool* skipFirstFrame,
|
||||
PRUint32* numAnimatedFrames,
|
||||
PRUint32* numIterations,
|
||||
PRUint32* frameDispose,
|
||||
PRUint32* frameBlend,
|
||||
PRUint32* frameDelay,
|
||||
PRUint32* offsetX,
|
||||
PRUint32* offsetY);
|
||||
void ConvertHostARGBRow(const PRUint8* aSrc, PRUint8* aDest,
|
||||
PRUint32 aPixelWidth, PRBool aUseTransparency);
|
||||
void StripAlpha(const PRUint8* aSrc, PRUint8* aDest,
|
||||
PRUint32 aPixelWidth);
|
||||
static void ErrorCallback(png_structp png_ptr, png_const_charp warning_msg);
|
||||
static void WriteCallback(png_structp png, png_bytep data, png_size_t size);
|
||||
|
||||
png_struct* mPNG;
|
||||
png_info* mPNGinfo;
|
||||
|
||||
PRBool mIsAnimation;
|
||||
|
||||
// image buffer
|
||||
PRUint8* mImageBuffer;
|
||||
PRUint32 mImageBufferSize;
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
* Contributor(s):
|
||||
* Stuart Parmenter <pavlov@pavlov.net>
|
||||
* Brett Wilson <brettw@gmail.com>
|
||||
* Justin Dolske <dolske@mozilla.com>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
|
@ -42,9 +43,55 @@
|
|||
/**
|
||||
* imgIEncoder interface
|
||||
*/
|
||||
[scriptable, uuid(CCC5B3AD-3E67-4e3d-97E1-B06B2E96FEF8)]
|
||||
[scriptable, uuid(ba3a854b-fb8d-4881-8af9-5849df10e5e5)]
|
||||
interface imgIEncoder : nsIInputStream
|
||||
{
|
||||
// Possible values for outputOptions. Multiple values are semicolon-separated.
|
||||
//
|
||||
// PNG:
|
||||
// ----
|
||||
// usetransparency=[yes|no|none] -- default: "yes"
|
||||
// Overrides default from input format. "no" and "none" are equivalent.
|
||||
//
|
||||
//
|
||||
// APNG:
|
||||
// -----
|
||||
// The following options can be used with startImageEncode():
|
||||
//
|
||||
// usetransparency=[yes|no|none] -- default: "yes"
|
||||
// Overrides default from input format. "no" and "none" are equivalent.
|
||||
// skipfirstframe=[yes|no] -- default: "no"
|
||||
// Controls display of the first frame in animations. PNG-only clients always
|
||||
// display the first frame (and only that frame).
|
||||
// frames=# -- default: "1"
|
||||
// Total number of frames in the image. The first frame, even if skipped, is
|
||||
// always included in the count.
|
||||
// plays=# -- default: "0"
|
||||
// Number of times to play the animation sequence. "0" will repeat forever.
|
||||
//
|
||||
//
|
||||
// The following options can be used for each frame, with addImageFrame():
|
||||
//
|
||||
// usetransparency=[yes|no|none] -- default: "yes"
|
||||
// Overrides default from input format. "no" and "none" are equivalent.
|
||||
// delay=# -- default: "500"
|
||||
// Number of milliseconds to display the frame, before moving to the next frame.
|
||||
// dispose=[none|background|previous] -- default: "none"
|
||||
// What to do with the image's canvas before rendering the next frame. See APNG spec.
|
||||
// blend=[source|over] -- default: "source"
|
||||
// How to render the new frame on the canvas. See APNG spec.
|
||||
// xoffset=# -- default: "0"
|
||||
// yoffset=# -- default: "0"
|
||||
// Where to draw the frame, relative to the canvas.
|
||||
//
|
||||
//
|
||||
// JPEG:
|
||||
// -----
|
||||
//
|
||||
// quality=# -- default: "50"
|
||||
// Quality of compression, 0-100 (worst-best).
|
||||
|
||||
|
||||
// Possible values for input format (note that not all image formats
|
||||
// support saving alpha channels):
|
||||
|
||||
|
@ -84,4 +131,27 @@ interface imgIEncoder : nsIInputStream
|
|||
in PRUint32 stride,
|
||||
in PRUint32 inputFormat,
|
||||
in AString outputOptions);
|
||||
|
||||
/*
|
||||
* For encoding images which may contain multiple frames, the 1-shot
|
||||
* initFromData() interface is too simplistic. The alternative is to
|
||||
* use startImageEncode(), call addImageFrame() one or more times, and
|
||||
* then finish initialization with endImageEncode().
|
||||
*
|
||||
* The arguments are basically the same as in initFromData().
|
||||
*/
|
||||
void startImageEncode(in PRUint32 width,
|
||||
in PRUint32 height,
|
||||
in PRUint32 inputFormat,
|
||||
in AString outputOptions);
|
||||
|
||||
void addImageFrame( [array, size_is(length), const] in PRUint8 data,
|
||||
in unsigned long length,
|
||||
in PRUint32 width,
|
||||
in PRUint32 height,
|
||||
in PRUint32 stride,
|
||||
in PRUint32 frameFormat,
|
||||
in AString frameOptions);
|
||||
|
||||
void endImageEncode();
|
||||
};
|
||||
|
|
|
@ -0,0 +1,465 @@
|
|||
/*
|
||||
* Test for APNG encoding in libpr0n
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
|
||||
// dispose=[none|background|previous]
|
||||
// blend=[source|over]
|
||||
|
||||
var apng1A = {
|
||||
// A 3x3 image with 3 frames, alternating red, green, blue. RGB format.
|
||||
width : 3, height : 3, skipFirstFrame : false,
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGB,
|
||||
transparency : null,
|
||||
plays : 0,
|
||||
|
||||
frames : [
|
||||
{ // frame #1
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
|
||||
transparency : null,
|
||||
|
||||
pixels : [
|
||||
255,0,0, 255,0,0, 255,0,0,
|
||||
255,0,0, 255,0,0, 255,0,0,
|
||||
255,0,0, 255,0,0, 255,0,0
|
||||
]
|
||||
},
|
||||
|
||||
{ // frame #2
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
|
||||
transparency : null,
|
||||
|
||||
pixels : [
|
||||
0,255,0, 0,255,0, 0,255,0,
|
||||
0,255,0, 0,255,0, 0,255,0,
|
||||
0,255,0, 0,255,0, 0,255,0
|
||||
],
|
||||
},
|
||||
|
||||
{ // frame #3
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGB, stride : 9,
|
||||
transparency : null,
|
||||
|
||||
pixels : [
|
||||
0,0,255, 0,0,255, 0,0,255,
|
||||
0,0,255, 0,0,255, 0,0,255,
|
||||
0,0,255, 0,0,255, 0,0,255
|
||||
],
|
||||
}
|
||||
|
||||
],
|
||||
expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAIAAADZSiLoAAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABBJREFUCJlj/M8ABUwMmCwAHVsBBdmfmJ4AAAAaZmNUTAAAAAEAAAADAAAAAwAAAAAAAAAAAfQD6AAA7mXKzAAAABZmZEFUAAAAAgiZY2T4zwABTAwMGCwAHFwBBVnd/YYAAAAaZmNUTAAAAAMAAAADAAAAAwAAAAAAAAAAAfQD6AAAA/MZJQAAABtmZEFUAAAABAiZY2Rg+M/AwMDAwMDEAAMIFgAbXQEFA/mlawAAAABJRU5ErkJggg=="
|
||||
};
|
||||
|
||||
|
||||
var apng1B = {
|
||||
// A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
|
||||
width : 3, height : 3, skipFirstFrame : false,
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA,
|
||||
transparency : null,
|
||||
plays : 0,
|
||||
|
||||
frames : [
|
||||
{ // frame #1
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255,
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255,
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255
|
||||
]
|
||||
},
|
||||
|
||||
{ // frame #2
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,255,0,255, 0,255,0,255, 0,255,0,255,
|
||||
0,255,0,255, 0,255,0,255, 0,255,0,255,
|
||||
0,255,0,255, 0,255,0,255, 0,255,0,255
|
||||
],
|
||||
},
|
||||
|
||||
{ // frame #3
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,0,255,255, 0,0,255,255, 0,0,255,255,
|
||||
0,0,255,255, 0,0,255,255, 0,0,255,255,
|
||||
0,0,255,255, 0,0,255,255, 0,0,255,255
|
||||
],
|
||||
}
|
||||
|
||||
],
|
||||
expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABVJREFUCJlj/M/A8J8BCpgYkAAKBwBJUwIE1lTE1gAAABpmY1RMAAAAAQAAAAMAAAADAAAAAAAAAAAB9APoAADuZcrMAAAAGWZkQVQAAAACCJljZPjP8J8BCpgYkAAKBwBIVAIEn70e9AAAABpmY1RMAAAAAwAAAAMAAAADAAAAAAAAAAAB9APoAAAD8xklAAAAGWZkQVQAAAAECJljZGD4/58BCpgYkAAKBwBHVQIEp0ldFQAAAABJRU5ErkJggg=="
|
||||
};
|
||||
|
||||
|
||||
var apng1C = {
|
||||
// A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
|
||||
// The first frame is skipped, so it will only flash green/blue (or static red in an APNG-unaware viewer)
|
||||
width : 3, height : 3, skipFirstFrame : true,
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA,
|
||||
transparency : null,
|
||||
plays : 0,
|
||||
|
||||
frames : [
|
||||
{ // frame #1
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255,
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255,
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255
|
||||
]
|
||||
},
|
||||
|
||||
{ // frame #2
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,255,0,255, 0,255,0,255, 0,255,0,255,
|
||||
0,255,0,255, 0,255,0,255, 0,255,0,255,
|
||||
0,255,0,255, 0,255,0,255, 0,255,0,255
|
||||
],
|
||||
},
|
||||
|
||||
{ // frame #3
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,0,255,255, 0,0,255,255, 0,0,255,255,
|
||||
0,0,255,255, 0,0,255,255, 0,0,255,255,
|
||||
0,0,255,255, 0,0,255,255, 0,0,255,255
|
||||
],
|
||||
}
|
||||
|
||||
],
|
||||
expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAACAAAAAPONk3AAAAAVSURBVAiZY/zPwPCfAQqYGJAACgcASVMCBNZUxNYAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABlmZEFUAAAAAQiZY2T4z/CfAQqYGJAACgcASFQCBKbFs7QAAAAaZmNUTAAAAAIAAAADAAAAAwAAAAAAAAAAAfQD6AAAmIDz8QAAABlmZEFUAAAAAwiZY2Rg+P+fAQqYGJAACgcAR1UCBMKQY1UAAAAASUVORK5CYII="
|
||||
};
|
||||
|
||||
|
||||
var apng2A = {
|
||||
// A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
|
||||
// blend = over mode
|
||||
width : 3, height : 3, skipFirstFrame : false,
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA,
|
||||
transparency : null,
|
||||
plays : 0,
|
||||
|
||||
frames : [
|
||||
{ // frame #1
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255,
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255,
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255
|
||||
]
|
||||
},
|
||||
|
||||
{ // frame #2
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "over", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,255,0,255, 0,255,0,180, 0,255,0,75,
|
||||
0,255,0,255, 0,255,0,180, 0,255,0,75,
|
||||
0,255,0,255, 0,255,0,180, 0,255,0,75
|
||||
],
|
||||
},
|
||||
|
||||
{ // frame #3
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "over", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,0,255,75, 0,0,255,75, 0,0,255,75,
|
||||
0,0,255,180, 0,0,255,180, 0,0,255,180,
|
||||
0,0,255,255, 0,0,255,255, 0,0,255,255
|
||||
],
|
||||
}
|
||||
|
||||
],
|
||||
expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABVJREFUCJlj/M/A8J8BCpgYkAAKBwBJUwIE1lTE1gAAABpmY1RMAAAAAQAAAAMAAAADAAAAAAAAAAAB9APoAAGZYvpaAAAAH2ZkQVQAAAACCJljYPjP8J/hP8MWhv8M3kwMSACFAwCjpAUABsIwXQAAABpmY1RMAAAAAwAAAAMAAAADAAAAAAAAAAAB9APoAAF09CmzAAAAHWZkQVQAAAAECJljZGD4780ABYwMDP+3IHP+wzgAZ+AE/2rZOPoAAAAASUVORK5CYII="
|
||||
};
|
||||
|
||||
|
||||
var apng2B = {
|
||||
// A 3x3 image with 3 frames, alternating red, green, blue. RGBA format.
|
||||
// blend = over, dispose = background
|
||||
width : 3, height : 3, skipFirstFrame : false,
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA,
|
||||
transparency : null,
|
||||
plays : 0,
|
||||
|
||||
frames : [
|
||||
{ // frame #1
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "background", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255,
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255,
|
||||
255,0,0,255, 255,0,0,255, 255,0,0,255
|
||||
]
|
||||
},
|
||||
|
||||
{ // frame #2
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "background", blend : "over", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,255,0,255, 0,255,0,180, 0,255,0,75,
|
||||
0,255,0,255, 0,255,0,180, 0,255,0,75,
|
||||
0,255,0,255, 0,255,0,180, 0,255,0,75
|
||||
],
|
||||
},
|
||||
|
||||
{ // frame #3
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "background", blend : "over", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,0,255,75, 0,0,255,75, 0,0,255,75,
|
||||
0,0,255,180, 0,0,255,180, 0,0,255,180,
|
||||
0,0,255,255, 0,0,255,255, 0,0,255,255
|
||||
],
|
||||
}
|
||||
|
||||
],
|
||||
expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAADAAAAAM7tusAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AEAbA0RWQAAABVJREFUCJlj/M/A8J8BCpgYkAAKBwBJUwIE1lTE1gAAABpmY1RMAAAAAQAAAAMAAAADAAAAAAAAAAAB9APoAQGAecsbAAAAH2ZkQVQAAAACCJljYPjP8J/hP8MWhv8M3kwMSACFAwCjpAUABsIwXQAAABpmY1RMAAAAAwAAAAMAAAADAAAAAAAAAAAB9APoAQFt7xjyAAAAHWZkQVQAAAAECJljZGD4780ABYwMDP+3IHP+wzgAZ+AE/2rZOPoAAAAASUVORK5CYII="
|
||||
};
|
||||
|
||||
|
||||
var apng3 = {
|
||||
// A 3x3 image with 4 frames. First frame is white, then 1x1 frames draw a diagonal line
|
||||
width : 3, height : 3, skipFirstFrame : false,
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA,
|
||||
transparency : null,
|
||||
plays : 0,
|
||||
|
||||
frames : [
|
||||
{ // frame #1
|
||||
width : 3, height : 3,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
|
||||
255,255,255,255, 255,255,255,255, 255,255,255,255,
|
||||
255,255,255,255, 255,255,255,255, 255,255,255,255,
|
||||
255,255,255,255, 255,255,255,255, 255,255,255,255
|
||||
]
|
||||
},
|
||||
|
||||
{ // frame #2
|
||||
width : 1, height : 1,
|
||||
x_offset : 0, y_offset : 0,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,0,0,255
|
||||
],
|
||||
},
|
||||
|
||||
{ // frame #3
|
||||
width : 1, height : 1,
|
||||
x_offset : 1, y_offset : 1,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,0,0,255
|
||||
],
|
||||
},
|
||||
|
||||
{ // frame #4
|
||||
width : 1, height : 1,
|
||||
x_offset : 2, y_offset : 2,
|
||||
dispose : "none", blend : "source", delay : 500,
|
||||
|
||||
format : Ci.imgIEncoder.INPUT_FORMAT_RGBA, stride : 12,
|
||||
|
||||
pixels : [
|
||||
0,0,0,255
|
||||
],
|
||||
}
|
||||
],
|
||||
|
||||
expected : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAADCAYAAABWKLW/AAAACGFjVEwAAAAEAAAAAHzNZtAAAAAaZmNUTAAAAAAAAAADAAAAAwAAAAAAAAAAAfQD6AAAdRYgGAAAABVJREFUCJlj/P///38GKGBiQAIoHACSCgQC2nCLJAAAABpmY1RMAAAAAQAAAAEAAAABAAAAAAAAAAAB9APoAAAyV32sAAAAEWZkQVQAAAACCJljYGBg+A8AAQQBAG6+ZqYAAAAaZmNUTAAAAAMAAAABAAAAAQAAAAEAAAABAfQD6AAAuDh6MQAAABFmZEFUAAAABAiZY2BgYPgPAAEEAQCWfC0QAAAAGmZjVEwAAAAFAAAAAQAAAAEAAAACAAAAAgH0A+gAAP34dNcAAAARZmRBVAAAAAYImWNgYGD4DwABBAEAdxLpvQAAAABJRU5ErkJggg=="
|
||||
};
|
||||
|
||||
// Main test entry point.
|
||||
function run_test() {
|
||||
dump("Checking apng1A...\n");
|
||||
run_test_for(apng1A);
|
||||
dump("Checking apng1B...\n");
|
||||
run_test_for(apng1B);
|
||||
dump("Checking apng1C...\n");
|
||||
run_test_for(apng1C);
|
||||
|
||||
dump("Checking apng2A...\n");
|
||||
run_test_for(apng2A);
|
||||
dump("Checking apng2B...\n");
|
||||
run_test_for(apng2B);
|
||||
|
||||
dump("Checking apng3...\n");
|
||||
run_test_for(apng3);
|
||||
};
|
||||
|
||||
|
||||
function run_test_for(input) {
|
||||
var encoder, dataURL;
|
||||
|
||||
encoder = encodeImage(input);
|
||||
dataURL = makeDataURL(encoder, "image/png");
|
||||
do_check_eq(dataURL, input.expected);
|
||||
};
|
||||
|
||||
|
||||
function encodeImage(input) {
|
||||
var encoder = Cc["@mozilla.org/image/encoder;2?type=image/png"].createInstance();
|
||||
encoder.QueryInterface(Ci.imgIEncoder);
|
||||
|
||||
var options = "";
|
||||
if (input.transparency) { options += "transparency=" + input.transparency; }
|
||||
options += ";frames=" + input.frames.length;
|
||||
options += ";skipfirstframe=" + (input.skipFirstFrame ? "yes" : "no");
|
||||
options += ";plays=" + input.plays;
|
||||
encoder.startImageEncode(input.width, input.height, input.format, options);
|
||||
|
||||
for (var i = 0; i < input.frames.length; i++) {
|
||||
var frame = input.frames[i];
|
||||
|
||||
options = "";
|
||||
if (frame.transparency) { options += "transparency=" + input.transparency; }
|
||||
options += ";delay=" + frame.delay;
|
||||
options += ";dispose=" + frame.dispose;
|
||||
options += ";blend=" + frame.blend;
|
||||
if (frame.x_offset > 0) { options += ";xoffset=" + frame.x_offset; }
|
||||
if (frame.y_offset > 0) { options += ";yoffset=" + frame.y_offset; }
|
||||
|
||||
encoder.addImageFrame(frame.pixels, frame.pixels.length,
|
||||
frame.width, frame.height, frame.stride, frame.format, options);
|
||||
}
|
||||
|
||||
encoder.endImageEncode();
|
||||
|
||||
return encoder;
|
||||
}
|
||||
|
||||
|
||||
function makeDataURL(encoder, mimetype) {
|
||||
var rawStream = encoder.QueryInterface(Ci.nsIInputStream);
|
||||
|
||||
var stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance();
|
||||
stream.QueryInterface(Ci.nsIBinaryInputStream);
|
||||
|
||||
stream.setInputStream(rawStream);
|
||||
|
||||
var bytes = stream.readByteArray(stream.available()); // returns int[]
|
||||
|
||||
var base64String = toBase64(bytes);
|
||||
|
||||
return "data:" + mimetype + ";base64," + base64String;
|
||||
}
|
||||
|
||||
/* toBase64 copied from extensions/xml-rpc/src/nsXmlRpcClient.js */
|
||||
|
||||
/* Convert data (an array of integers) to a Base64 string. */
|
||||
const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +
|
||||
'0123456789+/';
|
||||
const base64Pad = '=';
|
||||
function toBase64(data) {
|
||||
var result = '';
|
||||
var length = data.length;
|
||||
var i;
|
||||
// Convert every three bytes to 4 ascii characters.
|
||||
for (i = 0; i < (length - 2); i += 3) {
|
||||
result += toBase64Table[data[i] >> 2];
|
||||
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||
result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)];
|
||||
result += toBase64Table[data[i+2] & 0x3f];
|
||||
}
|
||||
|
||||
// Convert the remaining 1 or 2 bytes, pad out to 4 characters.
|
||||
if (length%3) {
|
||||
i = length - (length%3);
|
||||
result += toBase64Table[data[i] >> 2];
|
||||
if ((length%3) == 2) {
|
||||
result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)];
|
||||
result += toBase64Table[(data[i+1] & 0x0f) << 2];
|
||||
result += base64Pad;
|
||||
} else {
|
||||
result += toBase64Table[(data[i] & 0x03) << 4];
|
||||
result += base64Pad + base64Pad;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
Загрузка…
Ссылка в новой задаче