WebKit Bugzilla
New
Browse
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED FIXED
194746
drawImage() clears the canvas if it's the source of the image and globalCompositeOperation is "copy"
https://bugs.webkit.org/show_bug.cgi?id=194746
Summary
drawImage() clears the canvas if it's the source of the image and globalCompo...
susisu2413
Reported
2019-02-16 08:10:17 PST
When copying a region of a canvas to another region of the same canvas, drawImage does not work correctly if those regions have overlap. A reproduction code is here:
https://jsfiddle.net/kfcea804/
Expected: Two green overlapping rectangles are drawn. Actual: A part of one rectangle is chipped (drawn as white, not green). The chipped region matches the area of the source region that is overwritten by the drawImage. I'm not sure, but the cause of this issue may be that WebKit does not taking a copy of the source image before the drawing operation, as opposed to what HTML Living Standard says:
> When a canvas element is drawn onto itself, the drawing model requires the source to be copied before the image is drawn, so it is possible to copy parts of a canvas element onto overlapping parts of itself.
https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-drawimage
I am using Safari TP Release 75 (Safari 12.2, WebKit 14608.1.3.0.2) on macOS 10.14.3. I also checked that this issue is also reproduced with WebKit
r241645
.
Attachments
Patch
(6.34 KB, patch)
2019-02-19 15:30 PST
,
Said Abou-Hallawa
no flags
Details
Formatted Diff
Diff
Patch
(6.97 KB, patch)
2019-02-20 12:04 PST
,
Said Abou-Hallawa
no flags
Details
Formatted Diff
Diff
Show Obsolete
(1)
View All
Add attachment
proposed patch, testcase, etc.
Alexey Proskuryakov
Comment 1
2019-02-18 08:46:48 PST
Thank you for the report! Do you happen to know if this ever worked before in Safari?
Said Abou-Hallawa
Comment 2
2019-02-18 09:28:44 PST
Looking at the test case: The bug happens because of this setting: ctx.globalCompositeOperation = "copy"; Looking at the source code of CanvasRenderingContext2DBase::drawImage() and when if (state().globalComposite == CompositeCopy) { clearCanvas(); c->drawImageBuffer(*buffer, dstRect, srcRect, ImagePaintingOptions(state().globalComposite, state().globalBlend)); didDrawEntireCanvas(); } Calling clearCanvas() will clear the entire canvas rectangle. Because the test case clips to the destination rect(64, 64, 128, 128), clearCanvas() will only clear this rectangle. But this destination rectangle overlaps the source rectangle rect(0, 0, 128, 128). So the intersection rectangle (64, 64, 128, 128) is also cleared from the source rectangle. And this is why a white rectangle in the middle of the cnavas is drawn.
Simon Fraser (smfr)
Comment 3
2019-02-18 10:45:21 PST
So we need to read out the source before clearing in this case?
Radar WebKit Bug Importer
Comment 4
2019-02-18 10:46:24 PST
<
rdar://problem/48169601
>
susisu2413
Comment 5
2019-02-18 17:12:36 PST
(In reply to Alexey Proskuryakov from
comment #1
)
> Thank you for the report! Do you happen to know if this ever worked before > in Safari?
No, I don't know it worked before.
Said Abou-Hallawa
Comment 6
2019-02-19 15:30:41 PST
Created
attachment 362445
[details]
Patch
Simon Fraser (smfr)
Comment 7
2019-02-19 15:35:22 PST
Comment on
attachment 362445
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=362445&action=review
> Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:1665 > + if (&sourceCanvas == &canvasBase()) { > + auto imageBuffer = drawCanvasToBuffer(srcRect); > + clearCanvas(); > + c->drawImageBuffer(*imageBuffer, dstRect, { { }, srcRect.size() }, ImagePaintingOptions(state().globalComposite, state().globalBlend));
Should we look to see if srcRect and dstRect overlap and only do the copy in that case?
Said Abou-Hallawa
Comment 8
2019-02-19 16:29:23 PST
Comment on
attachment 362445
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=362445&action=review
>> Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:1665 >> + c->drawImageBuffer(*imageBuffer, dstRect, { { }, srcRect.size() }, ImagePaintingOptions(state().globalComposite, state().globalBlend)); > > Should we look to see if srcRect and dstRect overlap and only do the copy in that case?
No. Because if srcRect and dstRect do not overlap, we will fall through the else-statement which will clearCanvas() before drawImageBuffer() . For example, the new test layout in this patch will show nothing if I change the above if statement to be: if (&sourceCanvas == &canvasBase() && srcRect.intersects(dstRect))
Dean Jackson
Comment 9
2019-02-19 17:31:52 PST
Comment on
attachment 362445
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=362445&action=review
> Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:1602 > +std::unique_ptr<ImageBuffer> CanvasRenderingContext2DBase::drawCanvasToBuffer(const FloatRect& srcRect) const
I wonder if this should be called copyCanvasRegionToBuffer?
> Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:1611 > + auto& canvas = downcast<HTMLCanvasElement>(canvasBase()); > + ImageBuffer* srcBuffer = canvas.buffer(); > + if (!srcBuffer) > + return nullptr;
This can go first in the function to skip creating the dstBuffer if it fails.
Dean Jackson
Comment 10
2019-02-19 17:32:26 PST
Comment on
attachment 362445
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=362445&action=review
> LayoutTests/fast/canvas/canvas-drawImage-composite-copy.html:2 > + <canvas id="canvas" width="200" height="200"></canvas>
We should probably make this a WPT and submit it back.
Said Abou-Hallawa
Comment 11
2019-02-20 12:04:37 PST
Created
attachment 362522
[details]
Patch
Said Abou-Hallawa
Comment 12
2019-02-20 12:10:46 PST
Comment on
attachment 362445
[details]
Patch View in context:
https://bugs.webkit.org/attachment.cgi?id=362445&action=review
>> Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:1602 >> +std::unique_ptr<ImageBuffer> CanvasRenderingContext2DBase::drawCanvasToBuffer(const FloatRect& srcRect) const > > I wonder if this should be called copyCanvasRegionToBuffer?
I moved this function to ImageBuffer and I name it copyRectToBuffer(). So please have a look at the new patch.
>> Source/WebCore/html/canvas/CanvasRenderingContext2DBase.cpp:1611 >> + return nullptr; > > This can go first in the function to skip creating the dstBuffer if it fails.
You are right. After moving this function to ImageBuffer, I call it from CanvasRenderingContext2DBase::drawImage() from buffer->copyRectToBuffer(). We check nullability of buffer before this call. So this check is not needed anymore.
>> LayoutTests/fast/canvas/canvas-drawImage-composite-copy.html:2 >> + <canvas id="canvas" width="200" height="200"></canvas> > > We should probably make this a WPT and submit it back.
I will talk to Youenn Fablet about the process to get it as WPT.
WebKit Commit Bot
Comment 13
2019-02-20 14:28:19 PST
Comment on
attachment 362522
[details]
Patch Clearing flags on attachment: 362522 Committed
r241840
: <
https://trac.webkit.org/changeset/241840
>
WebKit Commit Bot
Comment 14
2019-02-20 14:28:21 PST
All reviewed patches have been landed. Closing bug.
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug