diff --git a/content/canvas/public/nsICanvasRenderingContextInternal.h b/content/canvas/public/nsICanvasRenderingContextInternal.h index 92c2d8634a49..e70f15f830aa 100644 --- a/content/canvas/public/nsICanvasRenderingContextInternal.h +++ b/content/canvas/public/nsICanvasRenderingContextInternal.h @@ -14,8 +14,8 @@ #include "mozilla/RefPtr.h" #define NS_ICANVASRENDERINGCONTEXTINTERNAL_IID \ -{ 0x8b8da863, 0xd151, 0x4014, \ - { 0x8b, 0xdc, 0x62, 0xb5, 0x0d, 0xc0, 0x2b, 0x62 } } +{ 0x9a6a5bdf, 0x1261, 0x4057, \ + { 0x85, 0xcc, 0xaf, 0x97, 0x6c, 0x36, 0x99, 0xa9 } } class gfxContext; class gfxASurface; @@ -71,6 +71,9 @@ public: GraphicsFilter aFilter, uint32_t aFlags = RenderFlagPremultAlpha) = 0; + // Creates an image buffer. Returns null on failure. + virtual void GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat) = 0; + // Gives you a stream containing the image represented by this context. // The format is given in aMimeTime, for example "image/png". // diff --git a/content/canvas/src/CanvasRenderingContext2D.cpp b/content/canvas/src/CanvasRenderingContext2D.cpp index dbe990e5f739..e760bb010295 100644 --- a/content/canvas/src/CanvasRenderingContext2D.cpp +++ b/content/canvas/src/CanvasRenderingContext2D.cpp @@ -41,7 +41,7 @@ #include "nsTArray.h" -#include "imgIEncoder.h" +#include "ImageEncoder.h" #include "gfxContext.h" #include "gfxASurface.h" @@ -1050,71 +1050,71 @@ CanvasRenderingContext2D::Render(gfxContext *ctx, GraphicsFilter aFilter, uint32 return rv; } -NS_IMETHODIMP -CanvasRenderingContext2D::GetInputStream(const char *aMimeType, - const PRUnichar *aEncoderOptions, - nsIInputStream **aStream) +void +CanvasRenderingContext2D::GetImageBuffer(uint8_t** aImageBuffer, + int32_t* aFormat) { - EnsureTarget(); - if (!IsTargetValid()) { - return NS_ERROR_FAILURE; - } + *aImageBuffer = nullptr; + *aFormat = 0; nsRefPtr surface; - - if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) { - return NS_ERROR_FAILURE; + nsresult rv = GetThebesSurface(getter_AddRefs(surface)); + if (NS_FAILED(rv)) { + return; } - nsresult rv; - const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type="; static const fallible_t fallible = fallible_t(); - nsAutoArrayPtr conid(new (fallible) char[strlen(encoderPrefix) + strlen(aMimeType) + 1]); - - if (!conid) { - return NS_ERROR_OUT_OF_MEMORY; - } - - strcpy(conid, encoderPrefix); - strcat(conid, aMimeType); - - nsCOMPtr encoder = do_CreateInstance(conid); - if (!encoder) { - return NS_ERROR_FAILURE; - } - - nsAutoArrayPtr imageBuffer(new (fallible) uint8_t[mWidth * mHeight * 4]); + uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4]; if (!imageBuffer) { - return NS_ERROR_OUT_OF_MEMORY; + return; } nsRefPtr imgsurf = - new gfxImageSurface(imageBuffer.get(), + new gfxImageSurface(imageBuffer, gfxIntSize(mWidth, mHeight), mWidth * 4, gfxImageFormatARGB32); if (!imgsurf || imgsurf->CairoStatus()) { - return NS_ERROR_FAILURE; + delete[] imageBuffer; + return; } nsRefPtr ctx = new gfxContext(imgsurf); - if (!ctx || ctx->HasError()) { - return NS_ERROR_FAILURE; + delete[] imageBuffer; + return; } ctx->SetOperator(gfxContext::OPERATOR_SOURCE); ctx->SetSource(surface, gfxPoint(0, 0)); ctx->Paint(); - rv = encoder->InitFromData(imageBuffer.get(), - mWidth * mHeight * 4, mWidth, mHeight, mWidth * 4, - imgIEncoder::INPUT_FORMAT_HOSTARGB, - nsDependentString(aEncoderOptions)); - NS_ENSURE_SUCCESS(rv, rv); + *aImageBuffer = imageBuffer; + *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB; +} - return CallQueryInterface(encoder, aStream); +NS_IMETHODIMP +CanvasRenderingContext2D::GetInputStream(const char *aMimeType, + const PRUnichar *aEncoderOptions, + nsIInputStream **aStream) +{ + uint8_t* imageBuffer = nullptr; + int32_t format = 0; + GetImageBuffer(&imageBuffer, &format); + if (!imageBuffer) { + return NS_ERROR_FAILURE; + } + + nsCString enccid("@mozilla.org/image/encoder;2?type="); + enccid += aMimeType; + nsCOMPtr encoder = do_CreateInstance(enccid.get()); + if (!encoder) { + return NS_ERROR_FAILURE; + } + + return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format, + encoder, aEncoderOptions, aStream); } SurfaceFormat @@ -3753,6 +3753,9 @@ NS_IMETHODIMP CanvasRenderingContext2D::GetThebesSurface(gfxASurface **surface) { EnsureTarget(); + if (!IsTargetValid()) { + return NS_ERROR_FAILURE; + } nsRefPtr thebesSurface = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget); diff --git a/content/canvas/src/CanvasRenderingContext2D.h b/content/canvas/src/CanvasRenderingContext2D.h index 7b4e25eee820..63e8df4cc68a 100644 --- a/content/canvas/src/CanvasRenderingContext2D.h +++ b/content/canvas/src/CanvasRenderingContext2D.h @@ -22,6 +22,7 @@ #include "mozilla/gfx/Rect.h" #include "mozilla/gfx/2D.h" #include "gfx2DGlue.h" +#include "imgIEncoder.h" class nsXULElement; @@ -454,6 +455,8 @@ public: friend class CanvasRenderingContext2DUserData; + virtual void GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat); + protected: nsresult GetImageDataArray(JSContext* aCx, int32_t aX, int32_t aY, uint32_t aWidth, uint32_t aHeight, diff --git a/content/canvas/src/Makefile.in b/content/canvas/src/Makefile.in index 8bc690245f7a..8d8fd0c65b35 100644 --- a/content/canvas/src/Makefile.in +++ b/content/canvas/src/Makefile.in @@ -22,5 +22,6 @@ INCLUDES += \ -I$(srcdir)/../../html/content/src \ -I$(srcdir)/../../../js/xpconnect/src \ -I$(srcdir)/../../../dom/base \ + -I$(srcdir)/../../../image/src \ -I$(topsrcdir)/content/xul/content/src \ $(NULL) diff --git a/content/canvas/src/WebGLContext.cpp b/content/canvas/src/WebGLContext.cpp index 78568dd07674..9c0589127e9c 100644 --- a/content/canvas/src/WebGLContext.cpp +++ b/content/canvas/src/WebGLContext.cpp @@ -27,7 +27,7 @@ #include "nsIVariant.h" -#include "imgIEncoder.h" +#include "ImageEncoder.h" #include "gfxContext.h" #include "gfxPattern.h" @@ -735,6 +735,54 @@ void WebGLContext::LoseOldestWebGLContextIfLimitExceeded() } } +void +WebGLContext::GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat) +{ + *aImageBuffer = nullptr; + *aFormat = 0; + + nsRefPtr imgsurf = + new gfxImageSurface(gfxIntSize(mWidth, mHeight), + gfxImageFormatARGB32); + + if (!imgsurf || imgsurf->CairoStatus()) { + return; + } + + nsRefPtr ctx = new gfxContext(imgsurf); + if (!ctx || ctx->HasError()) { + return; + } + + // Use Render() to make sure that appropriate y-flip gets applied + uint32_t flags = mOptions.premultipliedAlpha ? RenderFlagPremultAlpha : 0; + nsresult rv = Render(ctx, GraphicsFilter::FILTER_NEAREST, flags); + if (NS_FAILED(rv)) { + return; + } + + int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB; + if (!mOptions.premultipliedAlpha) { + // We need to convert to INPUT_FORMAT_RGBA, otherwise + // we are automatically considered premult, and unpremult'd. + // Yes, it is THAT silly. + // Except for different lossy conversions by color, + // we could probably just change the label, and not change the data. + gfxUtils::ConvertBGRAtoRGBA(imgsurf); + format = imgIEncoder::INPUT_FORMAT_RGBA; + } + + static const fallible_t fallible = fallible_t(); + uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4]; + if (!imageBuffer) { + return; + } + memcpy(imageBuffer, imgsurf->Data(), mWidth * mHeight * 4); + + *aImageBuffer = imageBuffer; + *aFormat = format; +} + NS_IMETHODIMP WebGLContext::GetInputStream(const char* aMimeType, const PRUnichar* aEncoderOptions, @@ -744,48 +792,22 @@ WebGLContext::GetInputStream(const char* aMimeType, if (!gl) return NS_ERROR_FAILURE; - nsRefPtr surf = new gfxImageSurface(gfxIntSize(mWidth, mHeight), - gfxImageFormatARGB32); - if (surf->CairoStatus() != 0) + uint8_t* imageBuffer = nullptr; + int32_t format = 0; + GetImageBuffer(&imageBuffer, &format); + if (!imageBuffer) { return NS_ERROR_FAILURE; - - nsRefPtr tmpcx = new gfxContext(surf); - // Use Render() to make sure that appropriate y-flip gets applied - uint32_t flags = mOptions.premultipliedAlpha ? RenderFlagPremultAlpha : 0; - nsresult rv = Render(tmpcx, GraphicsFilter::FILTER_NEAREST, flags); - if (NS_FAILED(rv)) - return rv; - - const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type="; - nsAutoArrayPtr conid(new char[strlen(encoderPrefix) + strlen(aMimeType) + 1]); - - strcpy(conid, encoderPrefix); - strcat(conid, aMimeType); - - nsCOMPtr encoder = do_CreateInstance(conid); - if (!encoder) - return NS_ERROR_FAILURE; - - int format = imgIEncoder::INPUT_FORMAT_HOSTARGB; - if (!mOptions.premultipliedAlpha) { - // We need to convert to INPUT_FORMAT_RGBA, otherwise - // we are automatically considered premult, and unpremult'd. - // Yes, it is THAT silly. - // Except for different lossy conversions by color, - // we could probably just change the label, and not change the data. - gfxUtils::ConvertBGRAtoRGBA(surf); - format = imgIEncoder::INPUT_FORMAT_RGBA; } - rv = encoder->InitFromData(surf->Data(), - mWidth * mHeight * 4, - mWidth, mHeight, - surf->Stride(), - format, - nsDependentString(aEncoderOptions)); - NS_ENSURE_SUCCESS(rv, rv); + nsCString enccid("@mozilla.org/image/encoder;2?type="); + enccid += aMimeType; + nsCOMPtr encoder = do_CreateInstance(enccid.get()); + if (!encoder) { + return NS_ERROR_FAILURE; + } - return CallQueryInterface(encoder, aStream); + return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format, + encoder, aEncoderOptions, aStream); } NS_IMETHODIMP diff --git a/content/canvas/src/WebGLContext.h b/content/canvas/src/WebGLContext.h index 69221452f3ad..8134d24c1957 100644 --- a/content/canvas/src/WebGLContext.h +++ b/content/canvas/src/WebGLContext.h @@ -169,6 +169,7 @@ public: NS_IMETHOD Render(gfxContext *ctx, GraphicsFilter f, uint32_t aFlags = RenderFlagPremultAlpha) MOZ_OVERRIDE; + virtual void GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat); NS_IMETHOD GetInputStream(const char* aMimeType, const PRUnichar* aEncoderOptions, nsIInputStream **aStream) MOZ_OVERRIDE; diff --git a/content/html/content/public/HTMLCanvasElement.h b/content/html/content/public/HTMLCanvasElement.h index f54bd13339ac..4ae642b4143d 100644 --- a/content/html/content/public/HTMLCanvasElement.h +++ b/content/html/content/public/HTMLCanvasElement.h @@ -227,10 +227,9 @@ protected: const JS::Value& aEncoderOptions, nsAString& aParams, bool* usingCustomParseOptions); - nsresult ExtractData(const nsAString& aType, + nsresult ExtractData(nsAString& aType, const nsAString& aOptions, - nsIInputStream** aStream, - bool& aFellBackToPNG); + nsIInputStream** aStream); nsresult ToDataURLImpl(JSContext* aCx, const nsAString& aMimeType, const JS::Value& aEncoderOptions, diff --git a/content/html/content/src/HTMLCanvasElement.cpp b/content/html/content/src/HTMLCanvasElement.cpp index 654d4839f83c..330ea6f7ae9a 100644 --- a/content/html/content/src/HTMLCanvasElement.cpp +++ b/content/html/content/src/HTMLCanvasElement.cpp @@ -5,11 +5,11 @@ #include "mozilla/dom/HTMLCanvasElement.h" -#include "Layers.h" -#include "imgIEncoder.h" +#include "ImageEncoder.h" #include "jsapi.h" #include "jsfriendapi.h" #include "gfxImageSurface.h" +#include "Layers.h" #include "mozilla/Base64.h" #include "mozilla/CheckedInt.h" #include "mozilla/dom/CanvasRenderingContext2D.h" @@ -23,6 +23,7 @@ #include "nsContentUtils.h" #include "nsDisplayList.h" #include "nsDOMFile.h" +#include "nsDOMJSUtils.h" #include "nsFrameManager.h" #include "nsIScriptSecurityManager.h" #include "nsITimer.h" @@ -46,29 +47,6 @@ namespace { typedef mozilla::dom::HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement HTMLImageOrCanvasOrVideoElement; -class ToBlobRunnable : public nsRunnable -{ -public: - ToBlobRunnable(mozilla::dom::FileCallback& aCallback, - nsIDOMBlob* aBlob) - : mCallback(&aCallback), - mBlob(aBlob) - { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - } - - NS_IMETHOD Run() - { - NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); - mozilla::ErrorResult rv; - mCallback->Call(mBlob, rv); - return rv.ErrorCode(); - } -private: - nsRefPtr mCallback; - nsCOMPtr mBlob; -}; - } // anonymous namespace namespace mozilla { @@ -369,10 +347,10 @@ HTMLCanvasElement::MozFetchAsStream(nsIInputStreamCallback *aCallback, return NS_ERROR_FAILURE; nsresult rv; - bool fellBackToPNG = false; nsCOMPtr inputData; - rv = ExtractData(aType, EmptyString(), getter_AddRefs(inputData), fellBackToPNG); + nsAutoString type(aType); + rv = ExtractData(type, EmptyString(), getter_AddRefs(inputData)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr asyncData = do_QueryInterface(inputData, &rv); @@ -404,68 +382,15 @@ HTMLCanvasElement::GetMozPrintCallback() const } nsresult -HTMLCanvasElement::ExtractData(const nsAString& aType, +HTMLCanvasElement::ExtractData(nsAString& aType, const nsAString& aOptions, - nsIInputStream** aStream, - bool& aFellBackToPNG) + nsIInputStream** aStream) { - // note that if we don't have a current context, the spec says we're - // supposed to just return transparent black pixels of the canvas - // dimensions. - nsRefPtr emptyCanvas; - nsIntSize size = GetWidthHeight(); - if (!mCurrentContext) { - emptyCanvas = new gfxImageSurface(gfxIntSize(size.width, size.height), gfxImageFormatARGB32); - if (emptyCanvas->CairoStatus()) { - return NS_ERROR_INVALID_ARG; - } - } - - nsresult rv; - - // get image bytes - nsCOMPtr imgStream; - NS_ConvertUTF16toUTF8 encoderType(aType); - - try_again: - if (mCurrentContext) { - rv = mCurrentContext->GetInputStream(encoderType.get(), - nsPromiseFlatString(aOptions).get(), - getter_AddRefs(imgStream)); - } else { - // no context, so we have to encode the empty image we created above - nsCString enccid("@mozilla.org/image/encoder;2?type="); - enccid += encoderType; - - nsCOMPtr encoder = do_CreateInstance(enccid.get(), &rv); - if (NS_SUCCEEDED(rv) && encoder) { - rv = encoder->InitFromData(emptyCanvas->Data(), - size.width * size.height * 4, - size.width, - size.height, - size.width * 4, - imgIEncoder::INPUT_FORMAT_HOSTARGB, - aOptions); - if (NS_SUCCEEDED(rv)) { - imgStream = do_QueryInterface(encoder); - } - } else { - rv = NS_ERROR_FAILURE; - } - } - - if (NS_FAILED(rv) && !aFellBackToPNG) { - // Try image/png instead. - // XXX ERRMSG we need to report an error to developers here! (bug 329026) - aFellBackToPNG = true; - encoderType.AssignLiteral("image/png"); - goto try_again; - } - - NS_ENSURE_SUCCESS(rv, rv); - - imgStream.forget(aStream); - return NS_OK; + return ImageEncoder::ExtractData(aType, + aOptions, + GetSize(), + mCurrentContext, + aStream); } nsresult @@ -516,8 +441,6 @@ HTMLCanvasElement::ToDataURLImpl(JSContext* aCx, const JS::Value& aEncoderOptions, nsAString& aDataURL) { - bool fallbackToPNG = false; - nsIntSize size = GetWidthHeight(); if (size.height == 0 || size.width == 0) { aDataURL = NS_LITERAL_STRING("data:,"); @@ -538,23 +461,18 @@ HTMLCanvasElement::ToDataURLImpl(JSContext* aCx, } nsCOMPtr stream; - rv = ExtractData(type, params, getter_AddRefs(stream), fallbackToPNG); + rv = ExtractData(type, params, getter_AddRefs(stream)); // If there are unrecognized custom parse options, we should fall back to // the default values for the encoder without any options at all. if (rv == NS_ERROR_INVALID_ARG && usingCustomParseOptions) { - fallbackToPNG = false; - rv = ExtractData(type, EmptyString(), getter_AddRefs(stream), fallbackToPNG); + rv = ExtractData(type, EmptyString(), getter_AddRefs(stream)); } NS_ENSURE_SUCCESS(rv, rv); // build data URL string - if (fallbackToPNG) - aDataURL = NS_LITERAL_STRING("data:image/png;base64,"); - else - aDataURL = NS_LITERAL_STRING("data:") + type + - NS_LITERAL_STRING(";base64,"); + aDataURL = NS_LITERAL_STRING("data:") + type + NS_LITERAL_STRING(";base64,"); uint64_t count; rv = stream->Available(&count); @@ -564,7 +482,6 @@ HTMLCanvasElement::ToDataURLImpl(JSContext* aCx, return Base64EncodeInputStream(stream, aDataURL, (uint32_t)count, aDataURL.Length()); } -// XXXkhuey the encoding should be off the main thread, but we're lazy. void HTMLCanvasElement::ToBlob(JSContext* aCx, FileCallback& aCallback, @@ -603,52 +520,24 @@ HTMLCanvasElement::ToBlob(JSContext* aCx, } #endif - bool fallbackToPNG = false; + nsCOMPtr scriptContext = + GetScriptContextFromJSContext(nsContentUtils::GetCurrentJSContext()); - nsCOMPtr stream; - aRv = ExtractData(type, params, getter_AddRefs(stream), fallbackToPNG); - // If there are unrecognized custom parse options, we should fall back to - // the default values for the encoder without any options at all. - if (aRv.ErrorCode() == NS_ERROR_INVALID_ARG && usingCustomParseOptions) { - fallbackToPNG = false; - aRv = ExtractData(type, EmptyString(), getter_AddRefs(stream), fallbackToPNG); + uint8_t* imageBuffer = nullptr; + int32_t format = 0; + if (mCurrentContext) { + mCurrentContext->GetImageBuffer(&imageBuffer, &format); } - if (aRv.Failed()) { - return; - } - - if (fallbackToPNG) { - type.AssignLiteral("image/png"); - } - - uint64_t imgSize; - aRv = stream->Available(&imgSize); - if (aRv.Failed()) { - return; - } - if (imgSize > UINT32_MAX) { - aRv.Throw(NS_ERROR_FILE_TOO_BIG); - return; - } - - void* imgData = nullptr; - aRv = NS_ReadInputStreamToBuffer(stream, &imgData, imgSize); - if (aRv.Failed()) { - return; - } - - // The DOMFile takes ownership of the buffer - nsRefPtr blob = - new nsDOMMemoryFile(imgData, imgSize, type); - - JSContext* cx = nsContentUtils::GetCurrentJSContext(); - if (cx) { - JS_updateMallocCounter(cx, imgSize); - } - - nsRefPtr runnable = new ToBlobRunnable(aCallback, blob); - aRv = NS_DispatchToCurrentThread(runnable); + aRv = ImageEncoder::ExtractDataAsync(type, + params, + usingCustomParseOptions, + imageBuffer, + format, + GetSize(), + mCurrentContext, + scriptContext, + aCallback); } already_AddRefed @@ -682,17 +571,10 @@ HTMLCanvasElement::MozGetAsFileImpl(const nsAString& aName, const nsAString& aType, nsIDOMFile** aResult) { - bool fallbackToPNG = false; - nsCOMPtr stream; - nsresult rv = ExtractData(aType, EmptyString(), getter_AddRefs(stream), - fallbackToPNG); - NS_ENSURE_SUCCESS(rv, rv); - nsAutoString type(aType); - if (fallbackToPNG) { - type.AssignLiteral("image/png"); - } + nsresult rv = ExtractData(type, EmptyString(), getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, rv); uint64_t imgSize; rv = stream->Available(&imgSize); diff --git a/content/html/content/src/Makefile.in b/content/html/content/src/Makefile.in index 1f56035a0740..abc70d66cd9c 100644 --- a/content/html/content/src/Makefile.in +++ b/content/html/content/src/Makefile.in @@ -22,6 +22,7 @@ INCLUDES += \ -I$(srcdir)/../../../../editor/libeditor/text \ -I$(srcdir)/../../../../editor/txmgr/src \ -I$(srcdir)/../../../../netwerk/base/src \ + -I$(srcdir)/../../../../content/canvas/src \ -I$(srcdir) \ -I$(topsrcdir)/xpcom/ds \ -I$(topsrcdir)/content/media/ \