Bug 769634 - imgITools should provide cropping functionality; r=bbondy
@ -9,7 +9,7 @@
|
||||
interface nsIInputStream;
|
||||
interface imgIContainer;
|
||||
|
||||
[scriptable, uuid(1f19a2ce-cf5c-4a6b-8ba7-63785b45053f)]
|
||||
[scriptable, uuid(8e16f39e-7012-46bd-aa22-2a7a3265608f)]
|
||||
interface imgITools : nsISupports
|
||||
{
|
||||
/**
|
||||
@ -60,7 +60,8 @@ interface imgITools : nsISupports
|
||||
* @param aMimeType
|
||||
* Type of encoded image desired (eg "image/png").
|
||||
* @param aWidth, aHeight
|
||||
* The size (in pixels) desired for the resulting image.
|
||||
* The size (in pixels) desired for the resulting image. Specify 0 to
|
||||
* use the given image's width or height. Values must be >= 0.
|
||||
* @param outputOptions
|
||||
* Encoder-specific output options.
|
||||
*/
|
||||
@ -69,4 +70,32 @@ interface imgITools : nsISupports
|
||||
in long aWidth,
|
||||
in long aHeight,
|
||||
[optional] in AString outputOptions);
|
||||
|
||||
/**
|
||||
* encodeCroppedImage
|
||||
* Caller provides an image container, and the mime type it should be
|
||||
* encoded to. We return an input stream for the encoded image data.
|
||||
* The encoded image is cropped to the specified dimensions.
|
||||
*
|
||||
* The given offset and size must not exceed the image bounds.
|
||||
*
|
||||
* @param aContainer
|
||||
* An image container.
|
||||
* @param aMimeType
|
||||
* Type of encoded image desired (eg "image/png").
|
||||
* @param aOffsetX, aOffsetY
|
||||
* The crop offset (in pixels). Values must be >= 0.
|
||||
* @param aWidth, aHeight
|
||||
* The size (in pixels) desired for the resulting image. Specify 0 to
|
||||
* use the given image's width or height. Values must be >= 0.
|
||||
* @param outputOptions
|
||||
* Encoder-specific output options.
|
||||
*/
|
||||
nsIInputStream encodeCroppedImage(in imgIContainer aContainer,
|
||||
in ACString aMimeType,
|
||||
in long aOffsetX,
|
||||
in long aOffsetY,
|
||||
in long aWidth,
|
||||
in long aHeight,
|
||||
[optional] in AString outputOptions);
|
||||
};
|
||||
|
@ -102,12 +102,14 @@ NS_IMETHODIMP imgTools::EncodeImage(imgIContainer *aContainer,
|
||||
const nsAString& aOutputOptions,
|
||||
nsIInputStream **aStream)
|
||||
{
|
||||
return EncodeScaledImage(aContainer,
|
||||
aMimeType,
|
||||
0,
|
||||
0,
|
||||
aOutputOptions,
|
||||
aStream);
|
||||
nsresult rv;
|
||||
|
||||
// Use frame 0 from the image container.
|
||||
nsRefPtr<gfxImageSurface> frame;
|
||||
rv = GetFirstImageFrame(aContainer, getter_AddRefs(frame));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return EncodeImageData(frame, aMimeType, aOutputOptions, aStream);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP imgTools::EncodeScaledImage(imgIContainer *aContainer,
|
||||
@ -117,20 +119,111 @@ NS_IMETHODIMP imgTools::EncodeScaledImage(imgIContainer *aContainer,
|
||||
const nsAString& aOutputOptions,
|
||||
nsIInputStream **aStream)
|
||||
{
|
||||
nsresult rv;
|
||||
bool doScaling = true;
|
||||
PRUint8 *bitmapData;
|
||||
PRUint32 bitmapDataLength, strideSize;
|
||||
NS_ENSURE_ARG(aScaledWidth >= 0 && aScaledHeight >= 0);
|
||||
|
||||
// If no scaled size is specified, we'll just encode the image at its
|
||||
// original size (no scaling).
|
||||
if (aScaledWidth == 0 && aScaledHeight == 0) {
|
||||
doScaling = false;
|
||||
} else {
|
||||
NS_ENSURE_ARG(aScaledWidth > 0);
|
||||
NS_ENSURE_ARG(aScaledHeight > 0);
|
||||
return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
|
||||
}
|
||||
|
||||
// Use frame 0 from the image container.
|
||||
nsRefPtr<gfxImageSurface> frame;
|
||||
nsresult rv = GetFirstImageFrame(aContainer, getter_AddRefs(frame));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
PRInt32 frameWidth = frame->Width(), frameHeight = frame->Height();
|
||||
|
||||
// If the given width or height is zero we'll replace it with the image's
|
||||
// original dimensions.
|
||||
if (aScaledWidth == 0) {
|
||||
aScaledWidth = frameWidth;
|
||||
} else if (aScaledHeight == 0) {
|
||||
aScaledHeight = frameHeight;
|
||||
}
|
||||
|
||||
// Create a temporary image surface
|
||||
nsRefPtr<gfxImageSurface> dest = new gfxImageSurface(gfxIntSize(aScaledWidth, aScaledHeight),
|
||||
gfxASurface::ImageFormatARGB32);
|
||||
gfxContext ctx(dest);
|
||||
|
||||
// Set scaling
|
||||
gfxFloat sw = (double) aScaledWidth / frameWidth;
|
||||
gfxFloat sh = (double) aScaledHeight / frameHeight;
|
||||
ctx.Scale(sw, sh);
|
||||
|
||||
// Paint a scaled image
|
||||
ctx.SetOperator(gfxContext::OPERATOR_SOURCE);
|
||||
ctx.SetSource(frame);
|
||||
ctx.Paint();
|
||||
|
||||
return EncodeImageData(dest, aMimeType, aOutputOptions, aStream);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP imgTools::EncodeCroppedImage(imgIContainer *aContainer,
|
||||
const nsACString& aMimeType,
|
||||
PRInt32 aOffsetX,
|
||||
PRInt32 aOffsetY,
|
||||
PRInt32 aWidth,
|
||||
PRInt32 aHeight,
|
||||
const nsAString& aOutputOptions,
|
||||
nsIInputStream **aStream)
|
||||
{
|
||||
NS_ENSURE_ARG(aOffsetX >= 0 && aOffsetY >= 0 && aWidth >= 0 && aHeight >= 0);
|
||||
|
||||
// Offsets must be zero when no width and height are given or else we're out
|
||||
// of bounds.
|
||||
NS_ENSURE_ARG(aWidth + aHeight > 0 || aOffsetX + aOffsetY == 0);
|
||||
|
||||
// If no size is specified then we'll preserve the image's original dimensions
|
||||
// and don't need to crop.
|
||||
if (aWidth == 0 && aHeight == 0) {
|
||||
return EncodeImage(aContainer, aMimeType, aOutputOptions, aStream);
|
||||
}
|
||||
|
||||
// Use frame 0 from the image container.
|
||||
nsRefPtr<gfxImageSurface> frame;
|
||||
nsresult rv = GetFirstImageFrame(aContainer, getter_AddRefs(frame));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
PRInt32 frameWidth = frame->Width(), frameHeight = frame->Height();
|
||||
|
||||
// If the given width or height is zero we'll replace it with the image's
|
||||
// original dimensions.
|
||||
if (aWidth == 0) {
|
||||
aWidth = frameWidth;
|
||||
} else if (aHeight == 0) {
|
||||
aHeight = frameHeight;
|
||||
}
|
||||
|
||||
// Check that the given crop rectangle is within image bounds.
|
||||
NS_ENSURE_ARG(frameWidth >= aOffsetX + aWidth &&
|
||||
frameHeight >= aOffsetY + aHeight);
|
||||
|
||||
// Create a temporary image surface
|
||||
nsRefPtr<gfxImageSurface> dest = new gfxImageSurface(gfxIntSize(aWidth, aHeight),
|
||||
gfxASurface::ImageFormatARGB32);
|
||||
gfxContext ctx(dest);
|
||||
|
||||
// Set translate
|
||||
ctx.Translate(gfxPoint(-aOffsetX, -aOffsetY));
|
||||
|
||||
// Paint a scaled image
|
||||
ctx.SetOperator(gfxContext::OPERATOR_SOURCE);
|
||||
ctx.SetSource(frame);
|
||||
ctx.Paint();
|
||||
|
||||
return EncodeImageData(dest, aMimeType, aOutputOptions, aStream);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP imgTools::EncodeImageData(gfxImageSurface *aSurface,
|
||||
const nsACString& aMimeType,
|
||||
const nsAString& aOutputOptions,
|
||||
nsIInputStream **aStream)
|
||||
{
|
||||
PRUint8 *bitmapData;
|
||||
PRUint32 bitmapDataLength, strideSize;
|
||||
|
||||
// Get an image encoder for the media type
|
||||
nsCAutoString encoderCID(
|
||||
NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=") + aMimeType);
|
||||
@ -139,60 +232,20 @@ NS_IMETHODIMP imgTools::EncodeScaledImage(imgIContainer *aContainer,
|
||||
if (!encoder)
|
||||
return NS_IMAGELIB_ERROR_NO_ENCODER;
|
||||
|
||||
// Use frame 0 from the image container.
|
||||
nsRefPtr<gfxImageSurface> frame;
|
||||
rv = aContainer->CopyFrame(imgIContainer::FRAME_CURRENT, true,
|
||||
getter_AddRefs(frame));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!frame)
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
|
||||
PRInt32 w = frame->Width(), h = frame->Height();
|
||||
if (!w || !h)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
nsRefPtr<gfxImageSurface> dest;
|
||||
|
||||
if (!doScaling) {
|
||||
// If we're not scaling the image, use the actual width/height.
|
||||
aScaledWidth = w;
|
||||
aScaledHeight = h;
|
||||
|
||||
bitmapData = frame->Data();
|
||||
bitmapData = aSurface->Data();
|
||||
if (!bitmapData)
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
strideSize = frame->Stride();
|
||||
bitmapDataLength = aScaledHeight * strideSize;
|
||||
strideSize = aSurface->Stride();
|
||||
|
||||
} else {
|
||||
// Prepare to draw a scaled version of the image to a temporary surface...
|
||||
|
||||
// Create a temporary image surface
|
||||
dest = new gfxImageSurface(gfxIntSize(aScaledWidth, aScaledHeight),
|
||||
gfxASurface::ImageFormatARGB32);
|
||||
gfxContext ctx(dest);
|
||||
|
||||
// Set scaling
|
||||
gfxFloat sw = (double) aScaledWidth / w;
|
||||
gfxFloat sh = (double) aScaledHeight / h;
|
||||
ctx.Scale(sw, sh);
|
||||
|
||||
// Paint a scaled image
|
||||
ctx.SetOperator(gfxContext::OPERATOR_SOURCE);
|
||||
ctx.SetSource(frame);
|
||||
ctx.Paint();
|
||||
|
||||
bitmapData = dest->Data();
|
||||
strideSize = dest->Stride();
|
||||
bitmapDataLength = aScaledHeight * strideSize;
|
||||
}
|
||||
PRInt32 width = aSurface->Width(), height = aSurface->Height();
|
||||
bitmapDataLength = height * strideSize;
|
||||
|
||||
// Encode the bitmap
|
||||
rv = encoder->InitFromData(bitmapData,
|
||||
nsresult rv = encoder->InitFromData(bitmapData,
|
||||
bitmapDataLength,
|
||||
aScaledWidth,
|
||||
aScaledHeight,
|
||||
width,
|
||||
height,
|
||||
strideSize,
|
||||
imgIEncoder::INPUT_FORMAT_HOSTARGB,
|
||||
aOutputOptions);
|
||||
@ -201,3 +254,17 @@ NS_IMETHODIMP imgTools::EncodeScaledImage(imgIContainer *aContainer,
|
||||
|
||||
return CallQueryInterface(encoder, aStream);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP imgTools::GetFirstImageFrame(imgIContainer *aContainer,
|
||||
gfxImageSurface **aSurface)
|
||||
{
|
||||
nsRefPtr<gfxImageSurface> frame;
|
||||
nsresult rv = aContainer->CopyFrame(imgIContainer::FRAME_CURRENT, true,
|
||||
getter_AddRefs(frame));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
NS_ENSURE_TRUE(frame, NS_ERROR_NOT_AVAILABLE);
|
||||
NS_ENSURE_TRUE(frame->Width() && frame->Height(), NS_ERROR_FAILURE);
|
||||
|
||||
frame.forget(aSurface);
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "imgITools.h"
|
||||
#include "gfxContext.h"
|
||||
|
||||
#define NS_IMGTOOLS_CID \
|
||||
{ /* fd9a9e8a-a77b-496a-b7bb-263df9715149 */ \
|
||||
@ -22,4 +23,13 @@ public:
|
||||
|
||||
imgTools();
|
||||
virtual ~imgTools();
|
||||
|
||||
private:
|
||||
NS_IMETHODIMP EncodeImageData(gfxImageSurface *aSurface,
|
||||
const nsACString& aMimeType,
|
||||
const nsAString& aOutputOptions,
|
||||
nsIInputStream **aStream);
|
||||
|
||||
NS_IMETHODIMP GetFirstImageFrame(imgIContainer *aContainer,
|
||||
gfxImageSurface **aSurface);
|
||||
};
|
||||
|
BIN
image/test/unit/image2jpg16x16cropped.jpg
Normal file
After Width: | Height: | Size: 879 B |
BIN
image/test/unit/image2jpg16x16cropped2.jpg
Normal file
After Width: | Height: | Size: 878 B |
BIN
image/test/unit/image2jpg16x32cropped3.jpg
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
image/test/unit/image2jpg16x32scaled.jpg
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
image/test/unit/image2jpg32x16cropped4.jpg
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
image/test/unit/image2jpg32x16scaled.jpg
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
image/test/unit/image2jpg32x32.jpg
Normal file
After Width: | Height: | Size: 1.6 KiB |
@ -431,6 +431,214 @@ referenceBytes = streamToArray(istream);
|
||||
compareArrays(encodedBytes, referenceBytes);
|
||||
|
||||
|
||||
/* ========== 15 ========== */
|
||||
testnum++;
|
||||
testdesc = "test cropping a JPG";
|
||||
|
||||
// 32x32 jpeg, 3494 bytes.
|
||||
imgName = "image2.jpg";
|
||||
inMimeType = "image/jpeg";
|
||||
imgFile = do_get_file(imgName);
|
||||
|
||||
istream = getFileInputStream(imgFile);
|
||||
do_check_eq(istream.available(), 3494);
|
||||
|
||||
outParam = {};
|
||||
imgTools.decodeImageData(istream, inMimeType, outParam);
|
||||
container = outParam.value;
|
||||
|
||||
// It's not easy to look at the pixel values from JS, so just
|
||||
// check the container's size.
|
||||
do_check_eq(container.width, 32);
|
||||
do_check_eq(container.height, 32);
|
||||
|
||||
// encode a cropped image
|
||||
istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 16, 16);
|
||||
encodedBytes = streamToArray(istream);
|
||||
|
||||
// Get bytes for exected result
|
||||
refName = "image2jpg16x16cropped.jpg";
|
||||
refFile = do_get_file(refName);
|
||||
istream = getFileInputStream(refFile);
|
||||
do_check_eq(istream.available(), 879);
|
||||
referenceBytes = streamToArray(istream);
|
||||
|
||||
// compare the encoder's output to the reference file.
|
||||
compareArrays(encodedBytes, referenceBytes);
|
||||
|
||||
|
||||
/* ========== 16 ========== */
|
||||
testnum++;
|
||||
testdesc = "test cropping a JPG with an offset";
|
||||
|
||||
// we'll reuse the container from the previous test
|
||||
istream = imgTools.encodeCroppedImage(container, "image/jpeg", 16, 16, 16, 16);
|
||||
encodedBytes = streamToArray(istream);
|
||||
|
||||
// Get bytes for exected result
|
||||
refName = "image2jpg16x16cropped2.jpg";
|
||||
refFile = do_get_file(refName);
|
||||
istream = getFileInputStream(refFile);
|
||||
do_check_eq(istream.available(), 878);
|
||||
referenceBytes = streamToArray(istream);
|
||||
|
||||
// compare the encoder's output to the reference file.
|
||||
compareArrays(encodedBytes, referenceBytes);
|
||||
|
||||
|
||||
/* ========== 17 ========== */
|
||||
testnum++;
|
||||
testdesc = "test cropping a JPG without a given height";
|
||||
|
||||
// we'll reuse the container from the previous test
|
||||
istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 16, 0);
|
||||
encodedBytes = streamToArray(istream);
|
||||
|
||||
// Get bytes for exected result
|
||||
refName = "image2jpg16x32cropped3.jpg";
|
||||
refFile = do_get_file(refName);
|
||||
istream = getFileInputStream(refFile);
|
||||
do_check_eq(istream.available(), 1127);
|
||||
referenceBytes = streamToArray(istream);
|
||||
|
||||
// compare the encoder's output to the reference file.
|
||||
compareArrays(encodedBytes, referenceBytes);
|
||||
|
||||
|
||||
/* ========== 18 ========== */
|
||||
testnum++;
|
||||
testdesc = "test cropping a JPG without a given width";
|
||||
|
||||
// we'll reuse the container from the previous test
|
||||
istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 0, 16);
|
||||
encodedBytes = streamToArray(istream);
|
||||
|
||||
// Get bytes for exected result
|
||||
refName = "image2jpg32x16cropped4.jpg";
|
||||
refFile = do_get_file(refName);
|
||||
istream = getFileInputStream(refFile);
|
||||
do_check_eq(istream.available(), 1135);
|
||||
referenceBytes = streamToArray(istream);
|
||||
|
||||
// compare the encoder's output to the reference file.
|
||||
compareArrays(encodedBytes, referenceBytes);
|
||||
|
||||
|
||||
/* ========== 19 ========== */
|
||||
testnum++;
|
||||
testdesc = "test cropping a JPG without a given width and height";
|
||||
|
||||
// we'll reuse the container from the previous test
|
||||
istream = imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 0, 0);
|
||||
encodedBytes = streamToArray(istream);
|
||||
|
||||
// Get bytes for exected result
|
||||
refName = "image2jpg32x32.jpg";
|
||||
refFile = do_get_file(refName);
|
||||
istream = getFileInputStream(refFile);
|
||||
do_check_eq(istream.available(), 1634);
|
||||
referenceBytes = streamToArray(istream);
|
||||
|
||||
// compare the encoder's output to the reference file.
|
||||
compareArrays(encodedBytes, referenceBytes);
|
||||
|
||||
|
||||
/* ========== 20 ========== */
|
||||
testnum++;
|
||||
testdesc = "test scaling a JPG without a given width";
|
||||
|
||||
// we'll reuse the container from the previous test
|
||||
istream = imgTools.encodeScaledImage(container, "image/jpeg", 0, 16);
|
||||
encodedBytes = streamToArray(istream);
|
||||
|
||||
// Get bytes for exected result
|
||||
refName = "image2jpg32x16scaled.jpg";
|
||||
refFile = do_get_file(refName);
|
||||
istream = getFileInputStream(refFile);
|
||||
do_check_eq(istream.available(), 1227);
|
||||
referenceBytes = streamToArray(istream);
|
||||
|
||||
// compare the encoder's output to the reference file.
|
||||
compareArrays(encodedBytes, referenceBytes);
|
||||
|
||||
|
||||
/* ========== 21 ========== */
|
||||
testnum++;
|
||||
testdesc = "test scaling a JPG without a given height";
|
||||
|
||||
// we'll reuse the container from the previous test
|
||||
istream = imgTools.encodeScaledImage(container, "image/jpeg", 16, 0);
|
||||
encodedBytes = streamToArray(istream);
|
||||
|
||||
// Get bytes for exected result
|
||||
refName = "image2jpg16x32scaled.jpg";
|
||||
refFile = do_get_file(refName);
|
||||
istream = getFileInputStream(refFile);
|
||||
do_check_eq(istream.available(), 1219);
|
||||
referenceBytes = streamToArray(istream);
|
||||
|
||||
// compare the encoder's output to the reference file.
|
||||
compareArrays(encodedBytes, referenceBytes);
|
||||
|
||||
|
||||
/* ========== 22 ========== */
|
||||
testnum++;
|
||||
testdesc = "test scaling a JPG without a given width and height";
|
||||
|
||||
// we'll reuse the container from the previous test
|
||||
istream = imgTools.encodeScaledImage(container, "image/jpeg", 0, 0);
|
||||
encodedBytes = streamToArray(istream);
|
||||
|
||||
// Get bytes for exected result
|
||||
refName = "image2jpg32x32.jpg";
|
||||
refFile = do_get_file(refName);
|
||||
istream = getFileInputStream(refFile);
|
||||
do_check_eq(istream.available(), 1634);
|
||||
referenceBytes = streamToArray(istream);
|
||||
|
||||
// compare the encoder's output to the reference file.
|
||||
compareArrays(encodedBytes, referenceBytes);
|
||||
|
||||
|
||||
/* ========== 22 ========== */
|
||||
testnum++;
|
||||
testdesc = "test invalid arguments for cropping";
|
||||
|
||||
var numErrors = 0;
|
||||
|
||||
try {
|
||||
// width/height can't be negative
|
||||
imgTools.encodeScaledImage(container, "image/jpeg", -1, -1);
|
||||
} catch (e) { numErrors++; }
|
||||
|
||||
try {
|
||||
// offsets can't be negative
|
||||
imgTools.encodeCroppedImage(container, "image/jpeg", -1, -1, 16, 16);
|
||||
} catch (e) { numErrors++; }
|
||||
|
||||
try {
|
||||
// width/height can't be negative
|
||||
imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, -1, -1);
|
||||
} catch (e) { numErrors++; }
|
||||
|
||||
try {
|
||||
// out of bounds
|
||||
imgTools.encodeCroppedImage(container, "image/jpeg", 17, 17, 16, 16);
|
||||
} catch (e) { numErrors++; }
|
||||
|
||||
try {
|
||||
// out of bounds
|
||||
imgTools.encodeCroppedImage(container, "image/jpeg", 0, 0, 33, 33);
|
||||
} catch (e) { numErrors++; }
|
||||
|
||||
try {
|
||||
// out of bounds
|
||||
imgTools.encodeCroppedImage(container, "image/jpeg", 1, 1, 0, 0);
|
||||
} catch (e) { numErrors++; }
|
||||
|
||||
do_check_eq(numErrors, 6);
|
||||
|
||||
|
||||
/* ========== bug 363986 ========== */
|
||||
testnum = 363986;
|
||||
testdesc = "test PNG and JPEG encoders' Read/ReadSegments methods";
|
||||
|