Bug 56238 - Non-premultiplied-alpha canvas attribute is ignore for toDataURL, drawImage, texImage2D
Summary: Non-premultiplied-alpha canvas attribute is ignore for toDataURL, drawImage, ...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebGL (show other bugs)
Version: 528+ (Nightly build)
Hardware: All All
: P2 Normal
Assignee: John Bauman
URL:
Keywords:
Depends on:
Blocks: 58425
  Show dependency treegraph
 
Reported: 2011-03-11 20:18 PST by John Bauman
Modified: 2011-04-13 11:21 PDT (History)
7 users (show)

See Also:


Attachments
Patch (28.31 KB, patch)
2011-03-11 20:25 PST, John Bauman
no flags Details | Formatted Diff | Diff
Patch (45.88 KB, patch)
2011-03-14 21:00 PDT, John Bauman
no flags Details | Formatted Diff | Diff
Patch (48.94 KB, patch)
2011-03-15 18:07 PDT, John Bauman
no flags Details | Formatted Diff | Diff
Patch (48.90 KB, patch)
2011-03-15 18:15 PDT, John Bauman
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description John Bauman 2011-03-11 20:18:07 PST
Currently WebKit treats every webgl canvas as having a premultiplied alpha, which causes a lot of problems for functions that need to either premultiply it (drawImage), or avoid converting it entirely (toDataURL, texImage2D).
Comment 1 John Bauman 2011-03-11 20:25:55 PST
Created attachment 85565 [details]
Patch
Comment 2 John Bauman 2011-03-11 20:27:26 PST
I'm not entirely sure that that's the right approach, but it works for Chromium and Safari and I'm sure it could be extended to Qt if I knew enough about Qt, so that's probably a sign.
Comment 3 Kenneth Russell 2011-03-14 14:56:06 PDT
Comment on attachment 85565 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=85565&action=review

This looks like it's on the right track to me but needs some more work.

Please incorporate the test, or portion thereof, which exercises this functionality using the script "update-webgl-conformance-tests". (cd into LayoutTests/fast/canvas/webgl, and run the script pointing it to the path of the test in the Khronos repo.)

> Source/WebCore/html/HTMLCanvasElement.cpp:347
> +    RefPtr<ImageData> imData = getImageData();

Avoid abbreviations in WebKit style. Just use "imageData".

> Source/WebCore/platform/graphics/GraphicsContext3D.h:864
> +    // Read rendering results into an RGBA pixel array.

The call sites indicate this produces BGRA data. Please update the comment or the spec of this method to indicate exactly what it does; please also mention whether the alpha channel is multiplied in or not.

> Source/WebCore/platform/graphics/ImageBuffer.h:136
> +    String ImageDataToDataURL(ImageData& input, const String& mimeType, const double* quality);

It would be nicer to put this in ImageData.h/cpp (WebCore/html/) but I see the reasons for putting it here. In particular, if you can achieve better code refactoring (see below), I think it's OK to leave it here. You could also consider making this a static private method, making ImageData a friend, and adding ImageData::todataURL().

Here, ImageData& should be const.

> Source/WebCore/platform/graphics/cg/ImageBufferCG.cpp:593
> +    if (!image)
> +        return "data:,";
> +        
> +    RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0));
> +    if (!data)
> +        return "data:,";
> +        
> +    RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
> +    ASSERT(uti);
> +        
> +    RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data.get(), uti.get(), 1, 0));
> +    if (!destination)
> +        return "data:,";
> +        
> +    RetainPtr<CFDictionaryRef> imageProperties = 0;
> +    if (CFEqual(uti.get(), jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
> +        // Apply the compression quality to the image destination.
> +        RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
> +        const void* key = kCGImageDestinationLossyCompressionQuality;
> +        const void* value = compressionQuality.get();
> +        imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
> +    }
> +        
> +    CGImageDestinationAddImage(destination.get(), image.get(), imageProperties.get());
> +    CGImageDestinationFinalize(destination.get());
> +    
> +    Vector<char> out;
> +    base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), out);
> +
> +    return makeString("data:", mimeType, ";base64,", out);

This block of code is exactly equal to that in ImageBuffer::toDataURL. Please refactor it into a new helper function.

> Source/WebCore/platform/graphics/qt/GraphicsContext3DQt.cpp:530
> +    return 0;

Add FIXME indicating this should be implemented.

> Source/WebCore/platform/graphics/skia/ImageBufferSkia.cpp:399
> +    Vector<unsigned char> encodedImage;
> +    if (mimeType == "image/jpeg") {
> +        int compressionQuality = JPEGImageEncoder::DefaultCompressionQuality;
> +        if (quality && *quality >= 0.0 && *quality <= 1.0)
> +            compressionQuality = static_cast<int>(*quality * 100 + 0.5);
> +        if (!JPEGImageEncoder::encode(source, compressionQuality, &encodedImage))
> +            return "data:,";
> +    } else {
> +        if (!PNGImageEncoder::encode(source, &encodedImage))
> +            return "data:,";
> +        ASSERT(mimeType == "image/png");
> +    }
> +
> +    Vector<char> base64Data;
> +    base64Encode(*reinterpret_cast<Vector<char>*>(&encodedImage), base64Data);
> +
> +    return makeString("data:", mimeType, ";base64,", base64Data);

There is some duplicated code between this and ImageBuffer::toDataURL above. It's less than in other places in this file but it would be good to refactor.

> Source/WebCore/platform/image-encoders/skia/JPEGImageEncoder.cpp:196
> +    imageSize.clampNegativeToZero();
> +    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 = handleError;
> +    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 = prepareOutput;
> +    cinfo.dest->empty_output_buffer = writeOutput;
> +    cinfo.dest->term_destination = finishOutput;
> +    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);
> +
> +    const unsigned char* pixels = imageData.data()->data()->data();
> +    row.resize(cinfo.image_width * cinfo.input_components);
> +    while (cinfo.next_scanline < cinfo.image_height) {
> +        RGBAtoRGB(pixels, cinfo.image_width, row.data());
> +        jpeg_write_scanlines(&cinfo, row.dataSlot(), 1);
> +        pixels += cinfo.image_width * 4;
> +    }
> +
> +    jpeg_finish_compress(&cinfo);
> +    jpeg_destroy_compress(&cinfo);
> +    return true;

Nearly all of this code is duplicated from encode(const SkBitmap& bitmap, ...). Please refactor it to avoid the duplication.

> Source/WebCore/platform/image-encoders/skia/PNGImageEncoder.cpp:154
> +    IntSize imageSize(bitmap.width(), bitmap.height());
> +    imageSize.clampNegativeToZero();
> +    unsigned char *pixels = bitmap.data()->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;
> +    }
> +
> +    // Optimize compression for speed.
> +    // The parameters are the same as what libpng uses by default for RGB and RGBA images, except:
> +    // - the zlib compression level is 3 instead of 6, to avoid the lazy Ziv-Lempel match searching;
> +    // - the delta filter is 1 ("sub") instead of 5 ("all"), to reduce the filter computations.
> +    // The zlib memory level (8) and strategy (Z_FILTERED) will be set inside libpng.
> +    //
> +    // Avoid the zlib strategies Z_HUFFMAN_ONLY or Z_RLE.
> +    // Although they are the fastest for poorly-compressible images (e.g. photographs),
> +    // they are very slow for highly-compressible images (e.g. text, drawings or business graphics).
> +    png_set_compression_level(png, 3);
> +    png_set_filter(png, PNG_FILTER_TYPE_BASE, PNG_FILTER_SUB);
> +
> +    png_set_write_fn(png, output, writeOutput, 0);
> +    png_set_IHDR(png, info, imageSize.width(), imageSize.height(),
> +                 8, PNG_COLOR_TYPE_RGB_ALPHA, 0, 0, 0);
> +    png_write_info(png, info);
> +
> +    for (int y = 0; y < imageSize.height(); ++y) {
> +        png_write_row(png, pixels);
> +        pixels += imageSize.width() * 4;
> +    }
> +
> +    png_write_end(png, info);
> +    png_destroy_write_struct(&png, &info);
> +    return true;

Same comment about duplicated code between here and encode(const SkBitmap& bitmap, ...).
Comment 4 John Bauman 2011-03-14 21:00:54 PDT
Created attachment 85767 [details]
Patch
Comment 5 Kenneth Russell 2011-03-15 14:37:03 PDT
Comment on attachment 85767 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=85767&action=review

The code changes look good but there are a few issues with the test case that need to be fixed.

> LayoutTests/fast/canvas/webgl/premultiplyalpha-test.html:49
> +     console.log(gl.getContextAttributes());

This should be removed.

> LayoutTests/fast/canvas/webgl/premultiplyalpha-test.html:70
> +     img.src = png;
> +     img.onload = function() {

There are issues like this race condition that I mentioned in the upstream bug report on Khronos. Please fix those and re-convert this test case into a layout test.

> LayoutTests/fast/canvas/webgl/resources/webgl-test-utils.js:109
> -  '    gl_FragColor = texture2D(tex, texCoord);\n' +
> +  '    gl_FragData[0] = texture2D(tex, texCoord);\n' +

This change is invalid. gl_FragData is unsupported in GLSL ES 2.0.

> LayoutTests/platform/chromium/test_expectations.txt:2360
> +BUGWEBGL : fast/canvas/webgl/premultiplyalpha-test.html = TEXT

Why is this new test not passing? It seems wrong that it doesn't pass even in Chromium.

> LayoutTests/platform/mac/Skipped:306
> +fast/canvas/webgl/premultipliedalpha-test.html

Since the new test doesn't involve drawing a WebGL canvas to the screen whose premultipliedAlpha flag is set to false, it seems that we should be able to make the new test pass on Mac as well. Is that not possible?

> Source/WebCore/platform/image-encoders/skia/PNGImageEncoder.cpp:106
> +            preMultipliedBGRAtoRGBA(pixels,
> +                                    imageSize.width(), row.data());

Line break here is unnecessary.
Comment 6 John Bauman 2011-03-15 15:00:03 PDT
(In reply to comment #5)
> 
> > LayoutTests/fast/canvas/webgl/resources/webgl-test-utils.js:109
> > -  '    gl_FragColor = texture2D(tex, texCoord);\n' +
> > +  '    gl_FragData[0] = texture2D(tex, texCoord);\n' +
> 
> This change is invalid. gl_FragData is unsupported in GLSL ES 2.0.

I got this straight from upstream. http://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.24.pdf says that gl_FragData is allowed.
> 
> > LayoutTests/platform/chromium/test_expectations.txt:2360
> > +BUGWEBGL : fast/canvas/webgl/premultiplyalpha-test.html = TEXT
> 
> Why is this new test not passing? It seems wrong that it doesn't pass even in Chromium.

Currently the test for the premultiplied case is broken, but with that fixed it should work on chromium and mac.
> 
> > LayoutTests/platform/mac/Skipped:306
> > +fast/canvas/webgl/premultipliedalpha-test.html
> 
> Since the new test doesn't involve drawing a WebGL canvas to the screen whose premultipliedAlpha flag is set to false, it seems that we should be able to make the new test pass on Mac as well. Is that not possible?
Comment 7 Kenneth Russell 2011-03-15 15:15:46 PDT
(In reply to comment #6)
> (In reply to comment #5)
> > 
> > > LayoutTests/fast/canvas/webgl/resources/webgl-test-utils.js:109
> > > -  '    gl_FragColor = texture2D(tex, texCoord);\n' +
> > > +  '    gl_FragData[0] = texture2D(tex, texCoord);\n' +
> > 
> > This change is invalid. gl_FragData is unsupported in GLSL ES 2.0.
> 
> I got this straight from upstream. http://www.khronos.org/registry/gles/specs/2.0/es_full_spec_2.0.24.pdf says that gl_FragData is allowed.

I apologize; I stand corrected.

> > > LayoutTests/platform/chromium/test_expectations.txt:2360
> > > +BUGWEBGL : fast/canvas/webgl/premultiplyalpha-test.html = TEXT
> > 
> > Why is this new test not passing? It seems wrong that it doesn't pass even in Chromium.
> 
> Currently the test for the premultiplied case is broken, but with that fixed it should work on chromium and mac.

What's the plan in this area? Are you going to fix the test upstream in Khronos? In that case, let's get it fixed there and then just update this copy of the test to match it.

> > 
> > > LayoutTests/platform/mac/Skipped:306
> > > +fast/canvas/webgl/premultipliedalpha-test.html
> > 
> > Since the new test doesn't involve drawing a WebGL canvas to the screen whose premultipliedAlpha flag is set to false, it seems that we should be able to make the new test pass on Mac as well. Is that not possible?
Comment 8 John Bauman 2011-03-15 18:07:26 PDT
Created attachment 85889 [details]
Patch
Comment 9 John Bauman 2011-03-15 18:15:31 PDT
Created attachment 85890 [details]
Patch
Comment 10 Kenneth Russell 2011-03-15 18:28:54 PDT
Comment on attachment 85890 [details]
Patch

Looks great. Excellent work.
Comment 11 WebKit Commit Bot 2011-03-15 20:27:04 PDT
Comment on attachment 85890 [details]
Patch

Clearing flags on attachment: 85890

Committed r81213: <http://trac.webkit.org/changeset/81213>
Comment 12 WebKit Commit Bot 2011-03-15 20:27:11 PDT
All reviewed patches have been landed.  Closing bug.
Comment 13 John Bauman 2011-03-16 15:44:18 PDT
*** Bug 55509 has been marked as a duplicate of this bug. ***