Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Canvas noise injection logic and improve support for gradients in post-processed ImageData #13199

Merged
merged 1 commit into from
May 11, 2023

Conversation

sysrqb
Copy link
Contributor

@sysrqb sysrqb commented Apr 26, 2023

709720d

Refactor Canvas noise injection logic and improve support for gradients in post-processed ImageData
https://bugs.webkit.org/show_bug.cgi?id=255993
rdar://107371244

Reviewed by NOBODY (OOPS!).

Gradients present an interesting problem when post-processing canvas ImageData
because tweaking colors result in significant visual anomalies. This change
introduces some naive gradient detection by looking at all of the immediate
neighbors of a pixel. We decide that a pixel is within a gradient if the
current color is between its opposing adjacent colors (e.g., above and below,
left and right, etc). If a color is decided to be within a gradient, then we
set those adjacent colors as bounds within which we can tweak the current
color.

In addition, within this change, we move the noise injection logic into its own
class, it removes the dependency on supplied colors for post-processing, and it
reduces the magnitude of noise from ~5% to ~1%.

* Source/WebCore/Headers.cmake:
* Source/WebCore/Sources.txt:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/html/CanvasBase.cpp:
(WebCore::CanvasBase::makeRenderingResultsAvailable):
(WebCore::CanvasBase::didDraw): We clip the rect before passing it into
CanvasNoiseInjection, and if the rect is std::nullopt then we pass in the
entire canvas.

(WebCore::CanvasBase::postProcessPixelBufferResults const):
(WebCore::CanvasBase::postProcessDirtyCanvasBuffer const): Deleted.
* Source/WebCore/html/CanvasBase.h:
* Source/WebCore/html/CanvasNoiseInjection.cpp: Added.
(WebCore::CanvasNoiseInjection::updateDirtyRect):
(WebCore::isIndexInBounds):
(WebCore::setTightnessBounds):
(WebCore::getGradientNeighbors):
(WebCore::CanvasNoiseInjection::postProcessDirtyCanvasBuffer):
(WebCore::CanvasNoiseInjection::postProcessPixelBufferResults const):
* Source/WebCore/html/CanvasNoiseInjection.h: Added.
* Source/WebCore/html/HTMLCanvasElement.cpp:
(WebCore::HTMLCanvasElement::getImageData):

@sysrqb sysrqb self-assigned this Apr 26, 2023
@sysrqb sysrqb added the New Bugs Unclassified bugs are placed in this component until the correct component can be determined. label Apr 26, 2023
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 26, 2023
@sysrqb sysrqb removed the merging-blocked Applied to prevent a change from being merged label May 3, 2023
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label May 3, 2023
@sysrqb sysrqb removed the merging-blocked Applied to prevent a change from being merged label May 9, 2023
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label May 9, 2023
@sysrqb sysrqb removed the merging-blocked Applied to prevent a change from being merged label May 10, 2023
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label May 10, 2023
@@ -373,6 +374,203 @@ bool CanvasBase::shouldInjectNoiseBeforeReadback() const
return scriptExecutionContext() && scriptExecutionContext()->noiseInjectionHashSalt();
}

struct RGBAColor {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be hesitant to add yet-another color type. Mostly this seems to be usable as uint32_t?
E.g ordering seems to be uint32_t ordering.

Alternatively you could perhaps try to use existing ColorComponents<> or something?

}
return tweak;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This noise algorithm now adds significant noise to already complex CanvasBase, doubling its size, and still it's a bit peripheral to the Canvas functionality.

Could we encapsulate the noise injection into a new file, such as CanvasNoiseInjection.h/cpp?

@@ -90,6 +90,8 @@ class CanvasBase {
void setOriginTainted() { m_originClean = false; }
bool originClean() const { return m_originClean; }

void setHadGradientFill() { m_hadGradientFill = true; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding custom ad-hoc state to the CanvasBase will result in hard to maintain Canvas implementation down the line.
Adding ad-hoc state to the algorithm creates a problem where you need to run security analysis on each possible state.
Would it be possible to implement one, stateless algorithm that would be good?

If the gradient algorithm is good enough for injecting noise for the gradient case, could it be used also in non-gradient case? This would fix the corner-cases where a gradient would be drawn to a canvas a, canvas a to canvas b, and canvas b would be read back, injecting the noise in b read.

bool isInLeftColumn = !(pixelIndex % size.width());
bool isInRightColumn = (pixelIndex % size.width()) == static_cast<unsigned>(size.width()) - 1;
bool isInTopLeftCorner = isInTopRow && isInLeftColumn;
bool isInBotomLeftCorner = isInBottomRow && isInLeftColumn;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo here

return { componentAt(offset), componentAt(offset + greenOffsetFromIndex), componentAt(offset + blueOffsetFromIndex), componentAt(offset + alphaOffsetFromIndex) };
}

static std::optional<std::pair<size_t, size_t>> getGradientNeighbors(size_t index, const Ref<PixelBuffer>&& pixelBuffer)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const Ref&& doesn't make much sense.
I think this means PixelBuffer&

@@ -411,6 +609,8 @@ bool CanvasBase::postProcessPixelBufferResults(Ref<PixelBuffer>&& pixelBuffer, c
HashMap<uint32_t, uint32_t> pixelColorMap;
bool wasPixelBufferModified { false };

HashMap<size_t, std::pair<size_t, size_t>> gradientIndiceMap;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand this map.
If I have 2000x2000 canvas with a full-screen gradient, does this map get 4 million entries? Sounds that it doesn't work in general case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure a fast algorithm to add visually non-distracting noise to a picture has been a solved research question for a while now. Maybe we could just look up a proper scientific noise kernel from some research paper and implement that?

@sysrqb sysrqb removed the merging-blocked Applied to prevent a change from being merged label May 11, 2023
@sysrqb sysrqb changed the title Improve support for gradient in post-processed ImageData Refactor Canvas noise injection logic and improve support for gradients in post-processed ImageData May 11, 2023
@webkit-early-warning-system
Copy link
Collaborator

Starting EWS tests for 6c7d857. Live statuses available at the PR page, #13199

@sysrqb sysrqb marked this pull request as ready for review May 11, 2023 06:35
@sysrqb sysrqb requested review from cdumez and rniwa as code owners May 11, 2023 06:35
}
}

bool CanvasNoiseInjection::postProcessPixelBufferResults(Ref<PixelBuffer>&& pixelBuffer, NoiseInjectionHashSalt salt) const
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we're saying "this method may take ownership of the reference to the pixel buffer."

The method actually never takes the ownership of the reference.
So the signature should just be PixelBuffer& which means "this method uses the pixel buffer".

mutable Lock m_imageBufferAssignmentLock;
mutable RefPtr<ImageBuffer> m_imageBuffer;
mutable size_t m_imageBufferCost { 0 };
mutable std::unique_ptr<GraphicsContextStateSaver> m_contextStateSaver;
mutable CanvasNoiseInjection m_canvasNoiseInjection;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems the CanvasNoiseInjection doesn't need to be mutable, as postprocessdirty is only used in mutable scope.

@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label May 11, 2023
@sysrqb sysrqb removed the merging-blocked Applied to prevent a change from being merged label May 11, 2023
@sysrqb sysrqb added the merge-queue Applied to send a pull request to merge-queue label May 11, 2023
@webkit-ews-buildbot webkit-ews-buildbot added merging-blocked Applied to prevent a change from being merged and removed merge-queue Applied to send a pull request to merge-queue labels May 11, 2023
@sysrqb sysrqb added merge-queue Applied to send a pull request to merge-queue merging-blocked Applied to prevent a change from being merged and removed merging-blocked Applied to prevent a change from being merged merge-queue Applied to send a pull request to merge-queue labels May 11, 2023
@sysrqb
Copy link
Contributor Author

sysrqb commented May 11, 2023

Now I'm confident enough that the gtk/wpe failures are flakey.

…ts in post-processed ImageData

https://bugs.webkit.org/show_bug.cgi?id=255993
rdar://107371244

Reviewed by Kimmo Kinnunen.

Gradients present an interesting problem when post-processing canvas ImageData
because tweaking colors result in significant visual anomalies. This change
introduces some naive gradient detection by looking at all of the immediate
neighbors of a pixel. We decide that a pixel is within a gradient if the
current color is between its opposing adjacent colors (e.g., above and below,
left and right, etc). If a color is decided to be within a gradient, then we
set those adjacent colors as bounds within which we can tweak the current
color.

In addition, within this change, we move the noise injection logic into its own
class, it removes the dependency on supplied colors for post-processing, and it
reduces the magnitude of noise from ~5% to ~1%.

* Source/WebCore/Headers.cmake:
* Source/WebCore/Sources.txt:
* Source/WebCore/WebCore.xcodeproj/project.pbxproj:
* Source/WebCore/html/CanvasBase.cpp:
(WebCore::CanvasBase::makeRenderingResultsAvailable):
(WebCore::CanvasBase::didDraw): We clip the rect before passing it into
CanvasNoiseInjection, and if the rect is std::nullopt then we pass in the
entire canvas.

(WebCore::CanvasBase::postProcessPixelBufferResults const):
(WebCore::CanvasBase::postProcessDirtyCanvasBuffer const): Deleted.
* Source/WebCore/html/CanvasBase.h:
* Source/WebCore/html/CanvasNoiseInjection.cpp: Added.
(WebCore::CanvasNoiseInjection::updateDirtyRect):
(WebCore::isIndexInBounds):
(WebCore::setTightnessBounds):
(WebCore::getGradientNeighbors):
(WebCore::CanvasNoiseInjection::postProcessDirtyCanvasBuffer):
(WebCore::CanvasNoiseInjection::postProcessPixelBufferResults const):
* Source/WebCore/html/CanvasNoiseInjection.h: Added.
* Source/WebCore/html/HTMLCanvasElement.cpp:
(WebCore::HTMLCanvasElement::getImageData):

Canonical link: https://commits.webkit.org/263979@main
@webkit-commit-queue
Copy link
Collaborator

Committed 263979@main (eabcb3e): https://commits.webkit.org/263979@main

Reviewed commits have been landed. Closing PR #13199 and removing active labels.

@webkit-commit-queue webkit-commit-queue removed the merge-queue Applied to send a pull request to merge-queue label May 11, 2023
@webkit-commit-queue webkit-commit-queue merged commit eabcb3e into WebKit:main May 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
New Bugs Unclassified bugs are placed in this component until the correct component can be determined.
Projects
None yet
5 participants