Implement canvas.toDataURL using PNGEncoder and JPEGEncoder
authorKJ Kim <gen.kim@samsung.com>
Wed, 30 Oct 2013 11:49:34 +0000 (20:49 +0900)
committerGerrit Code Review <gerrit@gerrit.vlan144.tizendev.org>
Thu, 31 Oct 2013 00:58:27 +0000 (00:58 +0000)
[Title] Implement canvas.toDataURL using PNGEncoder and JPEGEncoder
[Issue#] N/A
[Problem] toDataURL for PNG and JPEG is not supported
[Cause] PNGEncoding and JPEGEncoding is not supported in webkit cairo port
[Solution] implemented it like skia and gtk

Change-Id: I771cfe1eec4a96b7b285e4a9f53ea91778402d8e

Source/WTF/wtf/Platform.h
Source/WebCore/platform/MIMETypeRegistry.cpp
Source/WebCore/platform/graphics/cairo/CairoUtilities.cpp
Source/WebCore/platform/graphics/cairo/CairoUtilities.h
Source/WebCore/platform/graphics/cairo/ImageBufferCairo.cpp

index 263749e..b27691f 100644 (file)
 #define ENABLE_TIZEN_DISABLE_FORMSTATE_FOR_SELECT_ELEMENT 1 /* Mayur Vithal Kankanwadi (mayur.vk@samsung.com) : To disable formstate saving for select elements */
 #define ENABLE_TIZEN_DATAURL_SIZE_LIMIT 1 /* Divakar (divakar.a@samsung.com) : To limit data:image url size, in order to avoid low-memory condition. */
 #define ENABLE_TIZEN_FALLBACK_FONT_WEIGHT 1 /* Hyeonji Kim(hyeonji.kim@samsung.com) : Check fontDescription's weight to find the bold fallbackfont when font-weight is "bold" */
+#define ENABLE_TIZEN_CANVAS_TODATAURL_USING_IMAGE_ENCODER 1 /* Kyungjin Kim(gen.kim@samsung.com) : Implement canvas.toDataURL using PNGEncoder and JPEGEncoder */
 
 #if ENABLE(TEXT_AUTOSIZING)
 #define ENABLE_TIZEN_TEXT_AUTOSIZING 1 /* Jaehun Lim(ljaehun.lim@samsung.com) : for Tizen environment */
index 6c951c2..22c7ac0 100755 (executable)
@@ -298,6 +298,9 @@ static void initializeSupportedImageMIMETypesForEncoding()
     supportedImageMIMETypesForEncoding->add("image/ico");
 #elif USE(CAIRO)
     supportedImageMIMETypesForEncoding->add("image/png");
+#if ENABLE(TIZEN_CANVAS_TODATAURL_USING_IMAGE_ENCODER)
+    supportedImageMIMETypesForEncoding->add("image/jpeg");
+#endif
 #elif PLATFORM(BLACKBERRY)
     supportedImageMIMETypesForEncoding->add("image/png");
     supportedImageMIMETypesForEncoding->add("image/jpeg");
index d1b2d18..f3b1e64 100644 (file)
 #include <cairo-gl.h>
 #endif
 
+#if ENABLE(TIZEN_CANVAS_TODATAURL_USING_IMAGE_ENCODER)
+#include "ImageData.h"
+#include "png.h"
+#include "jpeglib.h"
+#include <setjmp.h>
+#endif
+
 namespace WebCore {
 
 void copyContextProperties(cairo_t* srcCr, cairo_t* dstCr)
@@ -488,4 +495,157 @@ void destroyCachedCairoSurface(void *data)
     cairo_surface_destroy(surface);
 }
 #endif
+
+#if ENABLE(TIZEN_CANVAS_TODATAURL_USING_IMAGE_ENCODER)
+/* Unpremultiplies data and converts native endian ARGB => RGBA bytes (from cairo unpremultiply_data) */
+static void preMultipliedARGBtoRGBACallback(png_structp png, png_row_infop row_info, png_bytep data)
+{
+    for (unsigned i = 0; i < row_info->rowbytes; i += 4) {
+        uint8_t* b = &data[i];
+        uint32_t pixel;
+
+        memcpy(&pixel, b, sizeof(uint32_t));
+        uint8_t alpha = (pixel & 0xff000000) >> 24;
+        if (!alpha)
+            b[0] = b[1] = b[2] = b[3] = 0;
+        else {
+            b[2] = (((pixel & 0xff0000) >> 16) * 255 + alpha / 2) / alpha;
+            b[1] = (((pixel & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha;
+            b[0] = (((pixel & 0x0000ff) >> 0) * 255 + alpha / 2) / alpha;
+            b[3] = alpha;
+        }
+    }
+}
+
+static void writePNGCallback(png_structp png, png_bytep data, png_size_t size)
+{
+    static_cast<Vector<unsigned char>*>(png_get_io_ptr(png))->append(data, size);
+}
+
+bool encodeImageDataToPNG(const ImageData& image, Vector<char>* output)
+{
+    IntSize imageSize = image.size();
+    unsigned char* inputPixels = image.data()->data();
+
+    png_struct* png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
+    png_info* info = png_create_info_struct(png);
+    if (!png || !info || setjmp(png_jmpbuf(png))) {
+        png_destroy_write_struct(png ? &png : 0, info ? &info : 0);
+        return false;
+    }
+
+    png_set_compression_level(png, 3);
+    png_set_filter(png, PNG_FILTER_TYPE_BASE, PNG_FILTER_SUB);
+
+    png_set_write_fn(png, output, writePNGCallback, 0);
+    png_set_IHDR(png, info, imageSize.width(), imageSize.height(), 8, PNG_COLOR_TYPE_RGB_ALPHA, 0, 0, 0);
+    png_write_info(png, info);
+    png_set_write_user_transform_fn(png, preMultipliedARGBtoRGBACallback);
+
+    const size_t pixelRowStride = imageSize.width() * 4;
+    for (int y = 0; y < imageSize.height(); ++y) {
+        png_write_row(png, inputPixels);
+        inputPixels += pixelRowStride;
+    }
+
+    png_write_end(png, info);
+    png_destroy_write_struct(&png, &info);
+
+    return true;
+}
+
+struct JPEGOutputBuffer : public jpeg_destination_mgr {
+    Vector<unsigned char>* output;
+    Vector<unsigned char> buffer;
+};
+
+static void jpegInitCallback(j_compress_ptr cinfo)
+{
+    JPEGOutputBuffer* out = static_cast<JPEGOutputBuffer*>(cinfo->dest);
+    out->buffer.resize(8192);
+    out->next_output_byte = out->buffer.data();
+    out->free_in_buffer = out->buffer.size();
+}
+
+static boolean jpegWriteCallback(j_compress_ptr cinfo)
+{
+    JPEGOutputBuffer* out = static_cast<JPEGOutputBuffer*>(cinfo->dest);
+    out->output->append(out->buffer.data(), out->buffer.size());
+    out->next_output_byte = out->buffer.data();
+    out->free_in_buffer = out->buffer.size();
+
+    return true;
+}
+
+static void jpegTerminateCallback(j_compress_ptr cinfo)
+{
+    JPEGOutputBuffer* out = static_cast<JPEGOutputBuffer*>(cinfo->dest);
+    const size_t size = out->buffer.size() - out->free_in_buffer;
+    out->output->append(out->buffer.data(), size);
+}
+
+static void jpegErrorCallback(j_common_ptr common)
+{
+    jmp_buf* jumpBufferPtr = static_cast<jmp_buf*>(common->client_data);
+    longjmp(*jumpBufferPtr, -1);
+}
+
+bool encodeImageDataToJPEG(const ImageData& image, int quality, Vector<unsigned char>* output)
+{
+    JPEGOutputBuffer destination;
+    destination.output = output;
+    Vector<JSAMPLE> row;
+
+    jpeg_compress_struct cinfo;
+    jpeg_error_mgr error;
+    cinfo.err = jpeg_std_error(&error);
+    error.error_exit = jpegErrorCallback;
+    jmp_buf jumpBuffer;
+    cinfo.client_data = &jumpBuffer;
+
+    if (setjmp(jumpBuffer)) {
+        jpeg_destroy_compress(&cinfo);
+        return false;
+    }
+
+    jpeg_create_compress(&cinfo);
+    cinfo.dest = &destination;
+    cinfo.dest->init_destination = jpegInitCallback;
+    cinfo.dest->empty_output_buffer = jpegWriteCallback;
+    cinfo.dest->term_destination = jpegTerminateCallback;
+
+    IntSize imageSize = image.size();
+    imageSize.clampNegativeToZero();
+    cinfo.image_height = imageSize.height();
+    cinfo.image_width = imageSize.width();
+
+    cinfo.in_color_space = JCS_RGB;
+    cinfo.input_components = 3;
+
+    jpeg_set_defaults(&cinfo);
+    jpeg_set_quality(&cinfo, quality, true);
+    jpeg_start_compress(&cinfo, true);
+
+    unsigned char* pixels = image.data()->data();;
+    row.resize(cinfo.image_width * cinfo.input_components);
+    const unsigned char* pixelEnd = pixels + cinfo.image_width * cinfo.image_height * 4;
+    while (pixels < pixelEnd) {
+        JSAMPLE* rowdata = row.data();
+        for (const unsigned char* rowEnd = pixels + cinfo.image_width * 4; pixels < rowEnd;) {
+            *rowdata++ = static_cast<JSAMPLE>(*pixels++ & 0xFF);
+            *rowdata++ = static_cast<JSAMPLE>(*pixels++ & 0xFF);
+            *rowdata++ = static_cast<JSAMPLE>(*pixels++ & 0xFF);
+            ++pixels;
+        }
+        rowdata = row.data();
+        jpeg_write_scanlines(&cinfo, &rowdata, 1);
+    }
+
+    jpeg_finish_compress(&cinfo);
+    jpeg_destroy_compress(&cinfo);
+
+    return true;
+}
+#endif
+
 } // namespace WebCore
index db2148b..43a20f5 100644 (file)
 
 #include "GraphicsTypes.h"
 #include <cairo.h>
+#if ENABLE(TIZEN_CANVAS_TODATAURL_USING_IMAGE_ENCODER)
+#include "ImageData.h"
+#include <wtf/Vector.h>
+#endif
 
 namespace WebCore {
 class AffineTransform;
@@ -55,6 +59,10 @@ void copyRectFromOneSurfaceToAnother(cairo_surface_t* from, cairo_surface_t* to,
 #if ENABLE(TIZEN_CAIROGLES_IMAGE_CACHE)
 void destroyCachedCairoSurface(void *data);
 #endif
+#if ENABLE(TIZEN_CANVAS_TODATAURL_USING_IMAGE_ENCODER)
+bool encodeImageDataToPNG(const ImageData&, Vector<char>*);
+bool encodeImageDataToJPEG(const ImageData&, int, Vector<unsigned char>*);
+#endif
 } // namespace WebCore
 
 #endif // CairoUtilities_h
index 9442d57..203c294 100755 (executable)
@@ -752,7 +752,7 @@ void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, c
 #endif
 }
 
-#if !PLATFORM(GTK)
+#if !PLATFORM(GTK) && !ENABLE(TIZEN_CANVAS_TODATAURL_USING_IMAGE_ENCODER)
 static cairo_status_t writeFunction(void* output, const unsigned char* data, unsigned int length)
 {
     if (!reinterpret_cast<Vector<unsigned char>*>(output)->tryAppend(data, length))
@@ -784,4 +784,36 @@ String ImageBuffer::toDataURL(const String& mimeType, const double*, CoordinateS
 }
 #endif
 
+#if ENABLE(TIZEN_CANVAS_TODATAURL_USING_IMAGE_ENCODER)
+static bool encodeImage(const ImageData& image, const String& mimeType, const double* quality, Vector<char>* output)
+{
+    if (mimeType == "image/jpeg") {
+        Vector<unsigned char>* encodedImage = reinterpret_cast<Vector<unsigned char>*>(output);
+        int compressionQuality = 65;
+        if (quality && *quality >= 0.0 && *quality <= 1.0)
+            compressionQuality = static_cast<int>(*quality * 100);
+        return encodeImageDataToJPEG(image, compressionQuality, encodedImage);
+    }
+
+    return encodeImageDataToPNG(image, output);
+}
+
+String ImageBuffer::toDataURL(const String& mimeType, const double* quality, CoordinateSystem) const
+{
+    ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
+
+    IntRect rect(0, 0, m_size.width(), m_size.height());
+    RefPtr<ImageData> imageData = ImageData::create(rect.size(), getPremultipliedImageData(rect));
+
+    Vector<char> encodedImage;
+    if (!imageData || !encodeImage(*imageData, mimeType, quality, &encodedImage))
+        return "data:,";
+
+    Vector<char> base64Data;
+    base64Encode(encodedImage, base64Data);
+
+    return "data:" + mimeType + ";base64," + base64Data;
+}
+#endif
+
 } // namespace WebCore