diff --git a/Tests/LibWeb/Screenshot/images/canvas-fillstyle-rgb.png b/Tests/LibWeb/Screenshot/images/canvas-fillstyle-rgb.png index 2ed1dbb35ca..7cdb91b126e 100644 Binary files a/Tests/LibWeb/Screenshot/images/canvas-fillstyle-rgb.png and b/Tests/LibWeb/Screenshot/images/canvas-fillstyle-rgb.png differ diff --git a/Tests/LibWeb/Screenshot/images/canvas-path-rect-ref.png b/Tests/LibWeb/Screenshot/images/canvas-path-rect-ref.png index 2d490ac6a66..49d9f12c73c 100644 Binary files a/Tests/LibWeb/Screenshot/images/canvas-path-rect-ref.png and b/Tests/LibWeb/Screenshot/images/canvas-path-rect-ref.png differ diff --git a/Tests/LibWeb/Screenshot/images/canvas-text-ref.png b/Tests/LibWeb/Screenshot/images/canvas-text-ref.png index 341eed2a723..8e6563188c9 100644 Binary files a/Tests/LibWeb/Screenshot/images/canvas-text-ref.png and b/Tests/LibWeb/Screenshot/images/canvas-text-ref.png differ diff --git a/Tests/LibWeb/Screenshot/images/svg-background-no-natural-size-ref.png b/Tests/LibWeb/Screenshot/images/svg-background-no-natural-size-ref.png index 931329120a5..62daf98f17b 100644 Binary files a/Tests/LibWeb/Screenshot/images/svg-background-no-natural-size-ref.png and b/Tests/LibWeb/Screenshot/images/svg-background-no-natural-size-ref.png differ diff --git a/Tests/LibWeb/Text/input/HTML/CanvasRenderingContext2D-get-image-data-correctness.html b/Tests/LibWeb/Text/input/HTML/CanvasRenderingContext2D-get-image-data-correctness.html index 1f39765c45a..485eab9c1de 100644 --- a/Tests/LibWeb/Text/input/HTML/CanvasRenderingContext2D-get-image-data-correctness.html +++ b/Tests/LibWeb/Text/input/HTML/CanvasRenderingContext2D-get-image-data-correctness.html @@ -12,7 +12,7 @@ let imageData = areaCtx.getImageData(0, 0, area.width, area.height); - if (imageData.data[0] == 0xff && imageData.data[1] == 0x80 && imageData.data[2] == 0x40) + if (imageData.data[0] == 0xff && imageData.data[1] == 0x80 && imageData.data[2] == 0x40 && imageData.data[3] == 0xff) println("PASS"); else println("FAIL"); diff --git a/Userland/Libraries/LibGfx/Color.h b/Userland/Libraries/LibGfx/Color.h index 2b4817fa6c3..0d782f2b805 100644 --- a/Userland/Libraries/LibGfx/Color.h +++ b/Userland/Libraries/LibGfx/Color.h @@ -281,6 +281,17 @@ public: return color_with_alpha; } + constexpr Color to_unpremultiplied() const + { + if (alpha() == 0 || alpha() == 255) + return *this; + return Color( + red() * 255 / alpha(), + green() * 255 / alpha(), + blue() * 255 / alpha(), + alpha()); + } + constexpr Color blend(Color source) const { if (alpha() == 0 || source.alpha() == 255) diff --git a/Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp b/Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp index 8d4da5abf11..c9ebd091355 100644 --- a/Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp +++ b/Userland/Libraries/LibGfx/EdgeFlagPathRasterizer.cpp @@ -29,7 +29,7 @@ static Vector prepare_edges(ReadonlySpan lines, unsigne // The first visible y value. auto top_clip = top_clip_scanline * int(samples_per_pixel); // The last visible y value. - auto bottom_clip = (bottom_clip_scanline + 1) * int(samples_per_pixel) - 1; + auto bottom_clip = (bottom_clip_scanline + 1) * int(samples_per_pixel); min_edge_y = bottom_clip; max_edge_y = top_clip; @@ -75,9 +75,8 @@ static Vector prepare_edges(ReadonlySpan lines, unsigne start_x += dxdy * (top_clip - min_y); min_y = top_clip; } - if (max_y > bottom_clip) { + if (max_y > bottom_clip) max_y = bottom_clip; - } min_edge_y = min(min_y, min_edge_y); max_edge_y = max(max_y, max_edge_y); @@ -240,8 +239,8 @@ Color EdgeFlagPathRasterizer::scanline_color(int scanline, int return function({ offset, scanline }); }); if (color.alpha() == 255) - return color.with_alpha(alpha); - return color.with_alpha(color.alpha() * alpha / 255); + return color.with_alpha(alpha, AlphaType::Premultiplied); + return color.with_alpha(color.alpha() * alpha / 255, AlphaType::Premultiplied); } template @@ -309,7 +308,7 @@ auto EdgeFlagPathRasterizer::accumulate_even_odd_scanline(EdgeE SampleType sample = init; VERIFY(edge_extent.min_x >= 0); VERIFY(edge_extent.max_x < static_cast(m_scanline.size())); - for (int x = edge_extent.min_x; x <= edge_extent.max_x; x += 1) { + for (int x = edge_extent.min_x; x <= edge_extent.max_x; x++) { sample ^= m_scanline.data()[x]; sample_callback(x, sample); m_scanline.data()[x] = 0; @@ -323,7 +322,7 @@ auto EdgeFlagPathRasterizer::accumulate_non_zero_scanline(EdgeE NonZeroAcc acc = init; VERIFY(edge_extent.min_x >= 0); VERIFY(edge_extent.max_x < static_cast(m_scanline.size())); - for (int x = edge_extent.min_x; x <= edge_extent.max_x; x += 1) { + for (int x = edge_extent.min_x; x <= edge_extent.max_x; x++) { if (auto edges = m_scanline.data()[x]) { // We only need to process the windings when we hit some edges. for (auto y_sub = 0u; y_sub < SamplesPerPixel; y_sub++) { diff --git a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp index 9e378e5ac42..083ac2621ad 100644 --- a/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp +++ b/Userland/Libraries/LibWeb/HTML/CanvasRenderingContext2D.cpp @@ -359,10 +359,15 @@ WebIDL::ExceptionOr> CanvasRenderingContext2D::get_image_da // 6. Set the pixel values of imageData to be the pixels of this's output bitmap in the area specified by the source rectangle in the bitmap's coordinate space units, converted from this's color space to imageData's colorSpace using 'relative-colorimetric' rendering intent. // FIXME: Can't use a Gfx::Painter + blit() here as it doesn't support ImageData bitmap's RGBA8888 format. + // NOTE: Internally we must use premultiplied alpha, but ImageData should hold unpremultiplied alpha. This conversion + // might result in a loss of precision, but is according to spec. + // See: https://html.spec.whatwg.org/multipage/canvas.html#premultiplied-alpha-and-the-2d-rendering-context + ASSERT(bitmap.alpha_type() == Gfx::AlphaType::Premultiplied); + ASSERT(image_data->bitmap().alpha_type() == Gfx::AlphaType::Unpremultiplied); for (int target_y = 0; target_y < source_rect_intersected.height(); ++target_y) { for (int target_x = 0; target_x < source_rect_intersected.width(); ++target_x) { auto pixel = bitmap.get_pixel(target_x + x, target_y + y); - image_data->bitmap().set_pixel(target_x, target_y, pixel); + image_data->bitmap().set_pixel(target_x, target_y, pixel.to_unpremultiplied()); } } diff --git a/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp b/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp index 0a5450432c9..d0f51ba2ee8 100644 --- a/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp +++ b/Userland/Libraries/LibWeb/HTML/HTMLCanvasElement.cpp @@ -212,7 +212,7 @@ bool HTMLCanvasElement::create_bitmap(size_t minimum_width, size_t minimum_heigh return false; } if (!m_bitmap || m_bitmap->size() != size) { - auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, size); + auto bitmap_or_error = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, size); if (bitmap_or_error.is_error()) return false; m_bitmap = bitmap_or_error.release_value_but_fixme_should_propagate_errors(); diff --git a/Userland/Libraries/LibWeb/HTML/ImageData.cpp b/Userland/Libraries/LibWeb/HTML/ImageData.cpp index dd9af815d5e..4709879ed12 100644 --- a/Userland/Libraries/LibWeb/HTML/ImageData.cpp +++ b/Userland/Libraries/LibWeb/HTML/ImageData.cpp @@ -30,7 +30,7 @@ WebIDL::ExceptionOr> ImageData::create(JS::Realm& re // 2. Initialize this given sw, sh, and settings set to settings. // 3. Initialize the image data of this to transparent black. auto data = TRY(JS::Uint8ClampedArray::create(realm, sw * sh * 4)); - auto bitmap = TRY_OR_THROW_OOM(vm, Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Premultiplied, Gfx::IntSize(sw, sh), sw * sizeof(u32), data->data().data())); + auto bitmap = TRY_OR_THROW_OOM(vm, Gfx::Bitmap::create_wrapper(Gfx::BitmapFormat::RGBA8888, Gfx::AlphaType::Unpremultiplied, Gfx::IntSize(sw, sh), sw * sizeof(u32), data->data().data())); return realm.heap().allocate(realm, realm, bitmap, data); } diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp index 28af712905a..5ab90d6a277 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp @@ -331,7 +331,8 @@ static SkColorType to_skia_color_type(Gfx::BitmapFormat format) static SkBitmap to_skia_bitmap(Gfx::Bitmap const& bitmap) { SkColorType color_type = to_skia_color_type(bitmap.format()); - SkImageInfo image_info = SkImageInfo::Make(bitmap.width(), bitmap.height(), color_type, kUnpremul_SkAlphaType); + SkAlphaType alpha_type = bitmap.alpha_type() == Gfx::AlphaType::Premultiplied ? kPremul_SkAlphaType : kUnpremul_SkAlphaType; + SkImageInfo image_info = SkImageInfo::Make(bitmap.width(), bitmap.height(), color_type, alpha_type); SkBitmap sk_bitmap; sk_bitmap.setInfo(image_info); diff --git a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp index d098d6f76d1..06a12a993c1 100644 --- a/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp +++ b/Userland/Libraries/LibWeb/SVG/SVGDecodedImageData.cpp @@ -87,7 +87,7 @@ void SVGDecodedImageData::visit_edges(Cell::Visitor& visitor) RefPtr SVGDecodedImageData::render(Gfx::IntSize size) const { - auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, size).release_value_but_fixme_should_propagate_errors(); + auto bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, Gfx::AlphaType::Premultiplied, size).release_value_but_fixme_should_propagate_errors(); VERIFY(m_document->navigable()); m_document->navigable()->set_viewport_size(size.to_type()); m_document->update_layout();