From 79638ae9eca9336c8071b8271ee9ac7598bb6fa6 Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Sat, 15 Mar 2025 23:02:57 -0400 Subject: [PATCH 01/14] think this is all it'll need to expose --- src/Graphics/Canvas/Offscreen.js | 0 src/Graphics/Canvas/Offscreen.purs | 9 +++++++++ 2 files changed, 9 insertions(+) create mode 100644 src/Graphics/Canvas/Offscreen.js create mode 100644 src/Graphics/Canvas/Offscreen.purs diff --git a/src/Graphics/Canvas/Offscreen.js b/src/Graphics/Canvas/Offscreen.js new file mode 100644 index 0000000..e69de29 diff --git a/src/Graphics/Canvas/Offscreen.purs b/src/Graphics/Canvas/Offscreen.purs new file mode 100644 index 0000000..81e0ea4 --- /dev/null +++ b/src/Graphics/Canvas/Offscreen.purs @@ -0,0 +1,9 @@ +module Canvas.Offscreen + ( OffscreenCanvas + , getHeight + , getWidth + , toBlob + , getContext + , transferToImageBitmap + ) where + From b2c83a5dead7957d7ad3612d1e0e3af24437c3c8 Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Sun, 16 Mar 2025 22:24:18 -0400 Subject: [PATCH 02/14] finally actually set up some basic functionality, plus some TODOs. including as I just realized uhh being able to actually obtain offscreen canvases --- bower.json | 4 +++- src/Graphics/Canvas/Offscreen.js | 23 +++++++++++++++++++++ src/Graphics/Canvas/Offscreen.purs | 32 +++++++++++++++++++++++++++--- 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/bower.json b/bower.json index bb8505d..141f9a0 100644 --- a/bower.json +++ b/bower.json @@ -24,6 +24,8 @@ "purescript-effect": "^4.0.0", "purescript-exceptions": "^6.0.0", "purescript-functions": "^6.0.0", - "purescript-maybe": "^6.0.0" + "purescript-maybe": "^6.0.0", + "purescript-media-types": "^6.0.0", + "purescript-web-file": "^4.0.0" } } diff --git a/src/Graphics/Canvas/Offscreen.js b/src/Graphics/Canvas/Offscreen.js index e69de29..50be44e 100644 --- a/src/Graphics/Canvas/Offscreen.js +++ b/src/Graphics/Canvas/Offscreen.js @@ -0,0 +1,23 @@ +export function getHeight(offscreen) { + return function() { + return offscreen.height; + }; +} + +export function getWidth(offscreen) { + return function() { + return offscreen.width; + }; +} + +export function toBlob(offscreen) { + return function(){ + return offscreen.toBlob(); + }; +} + +export function getContext2D(offscreen) { + return function(){ + return offscreen.getContext(); + }; +} diff --git a/src/Graphics/Canvas/Offscreen.purs b/src/Graphics/Canvas/Offscreen.purs index 81e0ea4..8cc545f 100644 --- a/src/Graphics/Canvas/Offscreen.purs +++ b/src/Graphics/Canvas/Offscreen.purs @@ -1,9 +1,35 @@ -module Canvas.Offscreen +module Graphics.Canvas.Offscreen ( OffscreenCanvas , getHeight , getWidth , toBlob - , getContext - , transferToImageBitmap + --, toBlob' + , getContext2D + -- , toImageBitmap ) where +import Effect (Effect) +import Web.File.Blob (Blob) +import Graphics.Canvas (Context2D) + +-- | An OffscreenCanvas object, representing a virtual canvas which does not exist +-- | in the document and will not be rendered. +foreign import data OffscreenCanvas :: Type + +-- | Gets the logical height in pixels of the virtual canvas. +foreign import getHeight :: OffscreenCanvas -> Effect Int + +-- | Gets the logical width in pixels of the virtual canvas. +foreign import getWidth :: OffscreenCanvas -> Effect Int + +-- | Creates a `Blob` of the image data on the virtual canvas, as a PNG file. +foreign import toBlob :: OffscreenCanvas -> Effect Blob + +-- -- | Creates a `Blob` of the image data on the virtual canvas, in the specified format. +-- TODO: add blob stuff to normal canvas too ;_; because I feel like this merits a type for +-- both restricting MediaTypes and for capturing which ones do and don't take a quality + +-- | Get the 2D graphics context for a virtual canvas element. +foreign import getContext2D :: OffscreenCanvas -> Effect Context2D + +-- TODO: focus long enough to figure out the image bitmap thing From baccde890bfc93fc7f61182b0aaa6bca4b5370f4 Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Sun, 16 Mar 2025 22:36:14 -0400 Subject: [PATCH 03/14] added constructor --- src/Graphics/Canvas/Offscreen.js | 6 ++++++ src/Graphics/Canvas/Offscreen.purs | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/src/Graphics/Canvas/Offscreen.js b/src/Graphics/Canvas/Offscreen.js index 50be44e..4b6a67f 100644 --- a/src/Graphics/Canvas/Offscreen.js +++ b/src/Graphics/Canvas/Offscreen.js @@ -1,3 +1,9 @@ +export function createOffscreenCanvasImpl(width, height) { + return function() { + return OffscreenCanvas(width, height); + }; +} + export function getHeight(offscreen) { return function() { return offscreen.height; diff --git a/src/Graphics/Canvas/Offscreen.purs b/src/Graphics/Canvas/Offscreen.purs index 8cc545f..c2f1984 100644 --- a/src/Graphics/Canvas/Offscreen.purs +++ b/src/Graphics/Canvas/Offscreen.purs @@ -1,5 +1,6 @@ module Graphics.Canvas.Offscreen ( OffscreenCanvas + , createOffscreenCanvas , getHeight , getWidth , toBlob @@ -11,11 +12,18 @@ module Graphics.Canvas.Offscreen import Effect (Effect) import Web.File.Blob (Blob) import Graphics.Canvas (Context2D) +import Data.Function.Uncurried (Fn2, runFn2) -- | An OffscreenCanvas object, representing a virtual canvas which does not exist -- | in the document and will not be rendered. foreign import data OffscreenCanvas :: Type +foreign import createOffscreenCanvasImpl :: Fn2 Int Int (Effect OffscreenCanvas) + +-- | Creates a virtual canvas with the given width and height. +createOffscreenCanvas :: Int -> Int -> Effect OffscreenCanvas +createOffscreenCanvas = runFn2 createOffscreenCanvasImpl + -- | Gets the logical height in pixels of the virtual canvas. foreign import getHeight :: OffscreenCanvas -> Effect Int From 05139fc3d4964afff2427fb9c6841d2942301b9d Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Sun, 16 Mar 2025 22:39:55 -0400 Subject: [PATCH 04/14] changed constructor params to record --- src/Graphics/Canvas/Offscreen.js | 4 ++-- src/Graphics/Canvas/Offscreen.purs | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Graphics/Canvas/Offscreen.js b/src/Graphics/Canvas/Offscreen.js index 4b6a67f..1daa10f 100644 --- a/src/Graphics/Canvas/Offscreen.js +++ b/src/Graphics/Canvas/Offscreen.js @@ -1,6 +1,6 @@ -export function createOffscreenCanvasImpl(width, height) { +export function createOffscreenCanvas(dims) { return function() { - return OffscreenCanvas(width, height); + return OffscreenCanvas(dims.width, dims.height); }; } diff --git a/src/Graphics/Canvas/Offscreen.purs b/src/Graphics/Canvas/Offscreen.purs index c2f1984..20e7a27 100644 --- a/src/Graphics/Canvas/Offscreen.purs +++ b/src/Graphics/Canvas/Offscreen.purs @@ -12,17 +12,13 @@ module Graphics.Canvas.Offscreen import Effect (Effect) import Web.File.Blob (Blob) import Graphics.Canvas (Context2D) -import Data.Function.Uncurried (Fn2, runFn2) -- | An OffscreenCanvas object, representing a virtual canvas which does not exist -- | in the document and will not be rendered. foreign import data OffscreenCanvas :: Type -foreign import createOffscreenCanvasImpl :: Fn2 Int Int (Effect OffscreenCanvas) - -- | Creates a virtual canvas with the given width and height. -createOffscreenCanvas :: Int -> Int -> Effect OffscreenCanvas -createOffscreenCanvas = runFn2 createOffscreenCanvasImpl +foreign import createOffscreenCanvas :: { width :: Int, height :: Int } -> (Effect OffscreenCanvas) -- | Gets the logical height in pixels of the virtual canvas. foreign import getHeight :: OffscreenCanvas -> Effect Int From dbce7c2f2e4022acdcc3bccac91eedcee77914b7 Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Sun, 16 Mar 2025 23:28:41 -0400 Subject: [PATCH 05/14] so I need to. I need to handle Promises in Offscreen. AND CALLBACK ASYNC in normal canvases. we love web apis wow --- src/Graphics/Canvas.js | 2 ++ src/Graphics/Canvas.purs | 30 ++++++++++++++++++++++++++++++ src/Graphics/Canvas/Offscreen.js | 2 +- src/Graphics/Canvas/Offscreen.purs | 21 +++++++++++++++------ 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/src/Graphics/Canvas.js b/src/Graphics/Canvas.js index 59c3dab..1d2c17c 100644 --- a/src/Graphics/Canvas.js +++ b/src/Graphics/Canvas.js @@ -474,6 +474,8 @@ export function createImageDataWith(arr) { }; } +// blob + export function drawImage(ctx) { return function(image_source) { return function(dx) { diff --git a/src/Graphics/Canvas.purs b/src/Graphics/Canvas.purs index 8e55bc4..cb15765 100644 --- a/src/Graphics/Canvas.purs +++ b/src/Graphics/Canvas.purs @@ -25,6 +25,7 @@ module Graphics.Canvas , RadialGradient , QuadraticCurve , BezierCurve + , BlobFormat(..) , getCanvasElementById , getContext2D @@ -99,6 +100,11 @@ module Graphics.Canvas , imageDataHeight , imageDataBuffer + -- , toBlob + -- , toBlob' + , blobPNG + , blobJPEG + , canvasElementToImageSource , drawImage , drawImageScale @@ -123,6 +129,8 @@ import Effect.Exception.Unsafe (unsafeThrow) import Data.ArrayBuffer.Types (Uint8ClampedArray) import Data.Function.Uncurried (Fn3, runFn3) import Data.Maybe (Maybe(..)) +import Data.MediaType (MediaType(..)) +import Data.MediaType.Common (imagePNG, imageJPEG, imageGIF) -- | A canvas HTML element. foreign import data CanvasElement :: Type @@ -651,6 +659,28 @@ foreign import imageDataHeight :: ImageData -> Int -- | Get the underlying buffer from an `ImageData` object. foreign import imageDataBuffer :: ImageData -> Uint8ClampedArray +-- | Format arguments for `toBlob'`. +-- | Quality for lossy compression is given as a `Number` between `0.0` and `1.0`. +data BlobFormat = Lossless MediaType | Lossy MediaType Number + +blobPNG :: BlobFormat +blobPNG = Lossless imagePNG + +blobJPEG :: Number -> BlobFormat +blobJPEG = Lossy imageJPEG + +-- HOORAY CALLBACK HELLLLLLLL oh boy this needs Aff doesn't it +-- -- | Create a `Blob` of the image data on the canvas, as a PNG file. +-- foreign import toBlob :: CanvasElement -> Effect Blob + +-- foreign import toBlobFormat :: Fn2 String CanvasElement (Effect Blob) +-- foreign import toBlobFormatQuality :: Fn3 String CanvasElement (Effect Blob) + +-- -- | Create a `Blob` of the image data on the canvas, in the specified format. +-- toBlob' :: BlobFormat -> CanvasElement -> Effect Blob +-- toBlob' (Lossless (MediaType format)) = runFn2 toBlobFormat format +-- toBlob' (Lossy (MediaType format) quality) = runFn3 toBlobFormatQuality format quality + foreign import drawImage :: Context2D -> CanvasImageSource -> Number -> Number -> Effect Unit foreign import drawImageScale :: Context2D -> CanvasImageSource -> Number -> Number -> Number -> Number -> Effect Unit diff --git a/src/Graphics/Canvas/Offscreen.js b/src/Graphics/Canvas/Offscreen.js index 1daa10f..ad25c84 100644 --- a/src/Graphics/Canvas/Offscreen.js +++ b/src/Graphics/Canvas/Offscreen.js @@ -24,6 +24,6 @@ export function toBlob(offscreen) { export function getContext2D(offscreen) { return function(){ - return offscreen.getContext(); + return offscreen.getContext('2d'); }; } diff --git a/src/Graphics/Canvas/Offscreen.purs b/src/Graphics/Canvas/Offscreen.purs index 20e7a27..3e126d4 100644 --- a/src/Graphics/Canvas/Offscreen.purs +++ b/src/Graphics/Canvas/Offscreen.purs @@ -4,14 +4,16 @@ module Graphics.Canvas.Offscreen , getHeight , getWidth , toBlob - --, toBlob' + , toBlob' , getContext2D -- , toImageBitmap ) where import Effect (Effect) import Web.File.Blob (Blob) -import Graphics.Canvas (Context2D) +import Graphics.Canvas (Context2D, BlobFormat(..)) +import Data.MediaType (MediaType(..)) +import Data.Function (Fn2, Fn3, runFn2, runFn3) -- | An OffscreenCanvas object, representing a virtual canvas which does not exist -- | in the document and will not be rendered. @@ -26,14 +28,21 @@ foreign import getHeight :: OffscreenCanvas -> Effect Int -- | Gets the logical width in pixels of the virtual canvas. foreign import getWidth :: OffscreenCanvas -> Effect Int --- | Creates a `Blob` of the image data on the virtual canvas, as a PNG file. +-- | Create a `Blob` of the image data on the virtual canvas, as a PNG file. foreign import toBlob :: OffscreenCanvas -> Effect Blob --- -- | Creates a `Blob` of the image data on the virtual canvas, in the specified format. --- TODO: add blob stuff to normal canvas too ;_; because I feel like this merits a type for --- both restricting MediaTypes and for capturing which ones do and don't take a quality +foreign import toBlobFormat :: Fn2 String OffscreenCanvas (Effect Blob) +foreign import toBlobFormatQuality :: Fn3 String OffscreenCanvas (Effect Blob) + +-- | Create a `Blob` of the image data on the virtual canvas, in the specified format. +toBlob' :: BlobFormat -> OffscreenCanvas -> Effect Blob +toBlob' (Lossless (MediaType format)) = runFn2 toBlobFormat format +toBlob' (Lossy (MediaType format) quality) = runFn3 toBlobFormatQuality format quality -- | Get the 2D graphics context for a virtual canvas element. +-- | Although this is a different type (`OffscreenCanvasRenderingContext2D`) +-- | in the underlying JavaScript, `Context2D` does not expose any of the +-- | discrepancies in functionality. foreign import getContext2D :: OffscreenCanvas -> Effect Context2D -- TODO: focus long enough to figure out the image bitmap thing From d4c129601309b8253e74a701192b82316fc9088e Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Mon, 17 Mar 2025 00:16:23 -0400 Subject: [PATCH 06/14] Offscreen blob stuff runs on Aff now. No idea if it works, granted --- bower.json | 5 +++++ src/Graphics/Canvas/Offscreen.js | 20 ++++++++++++++++---- src/Graphics/Canvas/Offscreen.purs | 21 ++++++++++++++------- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/bower.json b/bower.json index 141f9a0..a5df011 100644 --- a/bower.json +++ b/bower.json @@ -20,6 +20,8 @@ "url": "https://github.com/purescript-web/purescript-canvas.git" }, "dependencies": { + "purescript-aff": "^8.0.0", + "purescript-aff-promise": "^4.0.0", "purescript-arraybuffer-types": "^3.0.2", "purescript-effect": "^4.0.0", "purescript-exceptions": "^6.0.0", @@ -27,5 +29,8 @@ "purescript-maybe": "^6.0.0", "purescript-media-types": "^6.0.0", "purescript-web-file": "^4.0.0" + }, + "resolutions": { + "purescript-aff": "^7.0.0" } } diff --git a/src/Graphics/Canvas/Offscreen.js b/src/Graphics/Canvas/Offscreen.js index ad25c84..1f136d9 100644 --- a/src/Graphics/Canvas/Offscreen.js +++ b/src/Graphics/Canvas/Offscreen.js @@ -16,14 +16,26 @@ export function getWidth(offscreen) { }; } -export function toBlob(offscreen) { - return function(){ +export function toBlobDefault(offscreen) { + return function() { return offscreen.toBlob(); }; } +export function toBlobFormat(offscreen, format) { + return function() { + return offscreen.toBlob({type: format}); + }; +} + +export function toBlobFormatQuality(offscreen, format, quality) { + return function() { + return offscreen.toBlob({type: format, quality: quality}); + }; +} + export function getContext2D(offscreen) { - return function(){ - return offscreen.getContext('2d'); + return function() { + return offscreen.getContext("2d"); }; } diff --git a/src/Graphics/Canvas/Offscreen.purs b/src/Graphics/Canvas/Offscreen.purs index 3e126d4..c6de34e 100644 --- a/src/Graphics/Canvas/Offscreen.purs +++ b/src/Graphics/Canvas/Offscreen.purs @@ -9,11 +9,15 @@ module Graphics.Canvas.Offscreen -- , toImageBitmap ) where +import Prelude + import Effect (Effect) import Web.File.Blob (Blob) import Graphics.Canvas (Context2D, BlobFormat(..)) import Data.MediaType (MediaType(..)) -import Data.Function (Fn2, Fn3, runFn2, runFn3) +import Data.Function.Uncurried (Fn2, Fn3, runFn2, runFn3) +import Effect.Aff (Aff) +import Control.Promise (Promise, toAffE) -- | An OffscreenCanvas object, representing a virtual canvas which does not exist -- | in the document and will not be rendered. @@ -28,16 +32,19 @@ foreign import getHeight :: OffscreenCanvas -> Effect Int -- | Gets the logical width in pixels of the virtual canvas. foreign import getWidth :: OffscreenCanvas -> Effect Int +foreign import toBlobDefault :: OffscreenCanvas -> Effect (Promise Blob) + -- | Create a `Blob` of the image data on the virtual canvas, as a PNG file. -foreign import toBlob :: OffscreenCanvas -> Effect Blob +toBlob :: OffscreenCanvas -> Aff Blob +toBlob = toAffE <<< toBlobDefault -foreign import toBlobFormat :: Fn2 String OffscreenCanvas (Effect Blob) -foreign import toBlobFormatQuality :: Fn3 String OffscreenCanvas (Effect Blob) +foreign import toBlobFormat :: Fn2 String OffscreenCanvas (Effect (Promise Blob)) +foreign import toBlobFormatQuality :: Fn3 String Number OffscreenCanvas (Effect (Promise Blob)) -- | Create a `Blob` of the image data on the virtual canvas, in the specified format. -toBlob' :: BlobFormat -> OffscreenCanvas -> Effect Blob -toBlob' (Lossless (MediaType format)) = runFn2 toBlobFormat format -toBlob' (Lossy (MediaType format) quality) = runFn3 toBlobFormatQuality format quality +toBlob' :: BlobFormat -> OffscreenCanvas -> Aff Blob +toBlob' (Lossless (MediaType format)) = toAffE <<< runFn2 toBlobFormat format +toBlob' (Lossy (MediaType format) quality) = toAffE <<< runFn3 toBlobFormatQuality format quality -- | Get the 2D graphics context for a virtual canvas element. -- | Although this is a different type (`OffscreenCanvasRenderingContext2D`) From c2d952c407eb6428787811a5b3f9348f3e0e26ea Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Mon, 17 Mar 2025 00:18:25 -0400 Subject: [PATCH 07/14] ...forgot the constructor needs new --- src/Graphics/Canvas/Offscreen.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graphics/Canvas/Offscreen.js b/src/Graphics/Canvas/Offscreen.js index 1f136d9..53fe10b 100644 --- a/src/Graphics/Canvas/Offscreen.js +++ b/src/Graphics/Canvas/Offscreen.js @@ -1,6 +1,6 @@ export function createOffscreenCanvas(dims) { return function() { - return OffscreenCanvas(dims.width, dims.height); + return new OffscreenCanvas(dims.width, dims.height); }; } From 9f3d50cc8d138389c23d323cda55c80c16de2c24 Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Mon, 17 Mar 2025 01:26:19 -0400 Subject: [PATCH 08/14] what the hell is a canceller. is that a thing for other things but not this. this literally returns undefined. aaaaaaaaaaaa i hate js so muchhhhhhh --- bower.json | 5 +---- src/Graphics/Canvas.js | 12 +++++++++++- src/Graphics/Canvas.purs | 24 ++++++++++++++---------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/bower.json b/bower.json index a5df011..763e53a 100644 --- a/bower.json +++ b/bower.json @@ -20,7 +20,7 @@ "url": "https://github.com/purescript-web/purescript-canvas.git" }, "dependencies": { - "purescript-aff": "^8.0.0", + "purescript-aff": "^7.0.0", "purescript-aff-promise": "^4.0.0", "purescript-arraybuffer-types": "^3.0.2", "purescript-effect": "^4.0.0", @@ -29,8 +29,5 @@ "purescript-maybe": "^6.0.0", "purescript-media-types": "^6.0.0", "purescript-web-file": "^4.0.0" - }, - "resolutions": { - "purescript-aff": "^7.0.0" } } diff --git a/src/Graphics/Canvas.js b/src/Graphics/Canvas.js index 1d2c17c..7bf4ea7 100644 --- a/src/Graphics/Canvas.js +++ b/src/Graphics/Canvas.js @@ -474,7 +474,17 @@ export function createImageDataWith(arr) { }; } -// blob +// Wrapper into the error-argument callback style expected by Aff compat, +// with the error callback ignored. +function affBlob(_onError, onSuccess) { + return function(_cancelError, _onCancelerError, onCancelerSuccess) { + + }; +} + +export function toBlobDefault(canvas) { + +} export function drawImage(ctx) { return function(image_source) { diff --git a/src/Graphics/Canvas.purs b/src/Graphics/Canvas.purs index cb15765..46124aa 100644 --- a/src/Graphics/Canvas.purs +++ b/src/Graphics/Canvas.purs @@ -127,10 +127,12 @@ import Prelude import Effect (Effect) import Effect.Exception.Unsafe (unsafeThrow) import Data.ArrayBuffer.Types (Uint8ClampedArray) -import Data.Function.Uncurried (Fn3, runFn3) +import Data.Function.Uncurried (Fn2, Fn3, runFn2, runFn3) import Data.Maybe (Maybe(..)) import Data.MediaType (MediaType(..)) import Data.MediaType.Common (imagePNG, imageJPEG, imageGIF) +import Effect.Aff (Aff) +import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff) -- | A canvas HTML element. foreign import data CanvasElement :: Type @@ -669,17 +671,19 @@ blobPNG = Lossless imagePNG blobJPEG :: Number -> BlobFormat blobJPEG = Lossy imageJPEG --- HOORAY CALLBACK HELLLLLLLL oh boy this needs Aff doesn't it --- -- | Create a `Blob` of the image data on the canvas, as a PNG file. --- foreign import toBlob :: CanvasElement -> Effect Blob +foreign import toBlobDefault :: CanvasElement -> EffectFnAff Blob --- foreign import toBlobFormat :: Fn2 String CanvasElement (Effect Blob) --- foreign import toBlobFormatQuality :: Fn3 String CanvasElement (Effect Blob) +-- | Create a `Blob` of the image data on the canvas, as a PNG file. +toBlob :: CanvasElement -> Aff Blob +toBlob = fromEffectFnAff <<< toBlobDefault --- -- | Create a `Blob` of the image data on the canvas, in the specified format. --- toBlob' :: BlobFormat -> CanvasElement -> Effect Blob --- toBlob' (Lossless (MediaType format)) = runFn2 toBlobFormat format --- toBlob' (Lossy (MediaType format) quality) = runFn3 toBlobFormatQuality format quality +foreign import toBlobFormat :: Fn2 String CanvasElement (EffectFnAff Blob) +foreign import toBlobFormatQuality :: Fn3 String CanvasElement (EffectFnAff Blob) + +-- | Create a `Blob` of the image data on the canvas, in the specified format. +toBlob' :: BlobFormat -> CanvasElement -> Aff Blob +toBlob' (Lossless (MediaType format)) = fromEffectFnAff <<< runFn2 toBlobFormat format +toBlob' (Lossy (MediaType format) quality) = fromEffectFnAff <<< runFn3 toBlobFormatQuality format quality foreign import drawImage :: Context2D -> CanvasImageSource -> Number -> Number -> Effect Unit From 88894101423389b3de5feb21e92057095eea84da Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Mon, 17 Mar 2025 01:35:13 -0400 Subject: [PATCH 09/14] just when I was about to say, thank fuck for the JS ecosystem already having this solved... --- src/Graphics/Canvas.js | 30 ++++++++++++++++++++++++------ src/Graphics/Canvas.purs | 14 +++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/src/Graphics/Canvas.js b/src/Graphics/Canvas.js index 7bf4ea7..d9a4765 100644 --- a/src/Graphics/Canvas.js +++ b/src/Graphics/Canvas.js @@ -474,16 +474,34 @@ export function createImageDataWith(arr) { }; } -// Wrapper into the error-argument callback style expected by Aff compat, -// with the error callback ignored. -function affBlob(_onError, onSuccess) { - return function(_cancelError, _onCancelerError, onCancelerSuccess) { - +export function toBlobDefault(canvas) { + return function() { + return new Promise( + function(resolve, _reject) { + canvas.toBlob(resolve) + } + ); }; } -export function toBlobDefault(canvas) { +export function toBlobFormat(canvas, format) { + return function() { + return new Promise( + function(resolve, _reject) { + canvas.toBlob(resolve, {type: format}) + } + ); + }; +} +export function toBlobFormatQuality(canvas, format, quality) { + return function() { + return new Promise( + function(resolve, _reject) { + canvas.toBlob(resolve, {type: format, quality: quality}) + } + ); + }; } export function drawImage(ctx) { diff --git a/src/Graphics/Canvas.purs b/src/Graphics/Canvas.purs index 46124aa..5f89119 100644 --- a/src/Graphics/Canvas.purs +++ b/src/Graphics/Canvas.purs @@ -132,7 +132,7 @@ import Data.Maybe (Maybe(..)) import Data.MediaType (MediaType(..)) import Data.MediaType.Common (imagePNG, imageJPEG, imageGIF) import Effect.Aff (Aff) -import Effect.Aff.Compat (EffectFnAff, fromEffectFnAff) +import Control.Promise (Promise, toAffE) -- | A canvas HTML element. foreign import data CanvasElement :: Type @@ -671,19 +671,19 @@ blobPNG = Lossless imagePNG blobJPEG :: Number -> BlobFormat blobJPEG = Lossy imageJPEG -foreign import toBlobDefault :: CanvasElement -> EffectFnAff Blob +foreign import toBlobDefault :: CanvasElement -> Effect (Promise Blob) -- | Create a `Blob` of the image data on the canvas, as a PNG file. toBlob :: CanvasElement -> Aff Blob -toBlob = fromEffectFnAff <<< toBlobDefault +toBlob = toAffE <<< toBlobDefault -foreign import toBlobFormat :: Fn2 String CanvasElement (EffectFnAff Blob) -foreign import toBlobFormatQuality :: Fn3 String CanvasElement (EffectFnAff Blob) +foreign import toBlobFormat :: Fn2 String CanvasElement (Effect (Promise Blob)) +foreign import toBlobFormatQuality :: Fn3 String CanvasElement (Effect (Promise Blob)) -- | Create a `Blob` of the image data on the canvas, in the specified format. toBlob' :: BlobFormat -> CanvasElement -> Aff Blob -toBlob' (Lossless (MediaType format)) = fromEffectFnAff <<< runFn2 toBlobFormat format -toBlob' (Lossy (MediaType format) quality) = fromEffectFnAff <<< runFn3 toBlobFormatQuality format quality +toBlob' (Lossless (MediaType format)) = toAffE <<< runFn2 toBlobFormat format +toBlob' (Lossy (MediaType format) quality) = toAffE <<< runFn3 toBlobFormatQuality format quality foreign import drawImage :: Context2D -> CanvasImageSource -> Number -> Number -> Effect Unit From 56c56b50840607295819e159aa9f01edcc508f4f Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Mon, 17 Mar 2025 01:40:09 -0400 Subject: [PATCH 10/14] ESLint now respects underscores for ignored arguments --- .eslintrc.json | 4 ++++ src/Graphics/Canvas.js | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 3a97d05..ebe28de 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -20,6 +20,10 @@ "no-param-reassign": 2, "no-return-assign": 2, "no-unused-expressions": 2, + "no-unused-vars": [ + "error", + { "argsIgnorePattern": "^_" } + ], "no-use-before-define": 2, "radix": [2, "always"], "indent": [2, 2], diff --git a/src/Graphics/Canvas.js b/src/Graphics/Canvas.js index d9a4765..c68deab 100644 --- a/src/Graphics/Canvas.js +++ b/src/Graphics/Canvas.js @@ -478,7 +478,7 @@ export function toBlobDefault(canvas) { return function() { return new Promise( function(resolve, _reject) { - canvas.toBlob(resolve) + canvas.toBlob(resolve); } ); }; @@ -488,7 +488,7 @@ export function toBlobFormat(canvas, format) { return function() { return new Promise( function(resolve, _reject) { - canvas.toBlob(resolve, {type: format}) + canvas.toBlob(resolve, {type: format}); } ); }; @@ -498,7 +498,7 @@ export function toBlobFormatQuality(canvas, format, quality) { return function() { return new Promise( function(resolve, _reject) { - canvas.toBlob(resolve, {type: format, quality: quality}) + canvas.toBlob(resolve, {type: format, quality: quality}); } ); }; From 849e78b531bbe4f758008ab9f4cdea5cef3c26df Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Mon, 17 Mar 2025 13:39:06 -0400 Subject: [PATCH 11/14] aaaaaa it compiles thank you sm mikesol --- bower.json | 1 + src/Graphics/Canvas.js | 30 ++++++++++++------------------ src/Graphics/Canvas.purs | 30 +++++++++++++++++++----------- src/Graphics/Canvas/Offscreen.purs | 6 +++--- 4 files changed, 35 insertions(+), 32 deletions(-) diff --git a/bower.json b/bower.json index 763e53a..0bbb812 100644 --- a/bower.json +++ b/bower.json @@ -24,6 +24,7 @@ "purescript-aff-promise": "^4.0.0", "purescript-arraybuffer-types": "^3.0.2", "purescript-effect": "^4.0.0", + "purescript-either": "^6.1.0", "purescript-exceptions": "^6.0.0", "purescript-functions": "^6.0.0", "purescript-maybe": "^6.0.0", diff --git a/src/Graphics/Canvas.js b/src/Graphics/Canvas.js index c68deab..a363e9c 100644 --- a/src/Graphics/Canvas.js +++ b/src/Graphics/Canvas.js @@ -475,32 +475,26 @@ export function createImageDataWith(arr) { } export function toBlobDefault(canvas) { - return function() { - return new Promise( - function(resolve, _reject) { - canvas.toBlob(resolve); - } - ); + return function(cb) { + return function() { + return canvas.toBlob(cb); + }; }; } export function toBlobFormat(canvas, format) { - return function() { - return new Promise( - function(resolve, _reject) { - canvas.toBlob(resolve, {type: format}); - } - ); + return function(cb) { + return function() { + return canvas.toBlob(cb, {type: format}); + }; }; } export function toBlobFormatQuality(canvas, format, quality) { - return function() { - return new Promise( - function(resolve, _reject) { - canvas.toBlob(resolve, {type: format, quality: quality}); - } - ); + return function(cb) { + return function() { + return canvas.toBlob(cb, {type: format, quality: quality}); + }; }; } diff --git a/src/Graphics/Canvas.purs b/src/Graphics/Canvas.purs index 5f89119..5159dec 100644 --- a/src/Graphics/Canvas.purs +++ b/src/Graphics/Canvas.purs @@ -100,8 +100,8 @@ module Graphics.Canvas , imageDataHeight , imageDataBuffer - -- , toBlob - -- , toBlob' + , toBlob + , toBlob' , blobPNG , blobJPEG @@ -127,12 +127,13 @@ import Prelude import Effect (Effect) import Effect.Exception.Unsafe (unsafeThrow) import Data.ArrayBuffer.Types (Uint8ClampedArray) +import Data.Either (Either(Right)) import Data.Function.Uncurried (Fn2, Fn3, runFn2, runFn3) import Data.Maybe (Maybe(..)) import Data.MediaType (MediaType(..)) -import Data.MediaType.Common (imagePNG, imageJPEG, imageGIF) -import Effect.Aff (Aff) -import Control.Promise (Promise, toAffE) +import Data.MediaType.Common (imagePNG, imageJPEG) +import Web.File.Blob (Blob) +import Effect.Aff (Aff, makeAff) -- | A canvas HTML element. foreign import data CanvasElement :: Type @@ -665,25 +666,32 @@ foreign import imageDataBuffer :: ImageData -> Uint8ClampedArray -- | Quality for lossy compression is given as a `Number` between `0.0` and `1.0`. data BlobFormat = Lossless MediaType | Lossy MediaType Number +-- Testing in my Firefox, these are the only two types supported, +-- with no support for `image/gif` or `image/svg+xml`... but is that standard? + blobPNG :: BlobFormat blobPNG = Lossless imagePNG blobJPEG :: Number -> BlobFormat blobJPEG = Lossy imageJPEG -foreign import toBlobDefault :: CanvasElement -> Effect (Promise Blob) +-- Thanks to mikesol for letting me know about makeAff! +blobCBToAff :: ((Blob -> Effect Unit) -> Effect Unit) -> Aff Blob +blobCBToAff b = makeAff \cb -> b (cb <<< Right) $> mempty + +foreign import toBlobDefault :: CanvasElement -> (Blob -> Effect Unit) -> Effect Unit -- | Create a `Blob` of the image data on the canvas, as a PNG file. toBlob :: CanvasElement -> Aff Blob -toBlob = toAffE <<< toBlobDefault +toBlob = blobCBToAff <<< toBlobDefault -foreign import toBlobFormat :: Fn2 String CanvasElement (Effect (Promise Blob)) -foreign import toBlobFormatQuality :: Fn3 String CanvasElement (Effect (Promise Blob)) +foreign import toBlobFormat :: Fn2 String CanvasElement ((Blob -> Effect Unit) -> Effect Unit) +foreign import toBlobFormatQuality :: Fn3 String Number CanvasElement ((Blob -> Effect Unit) -> Effect Unit) -- | Create a `Blob` of the image data on the canvas, in the specified format. toBlob' :: BlobFormat -> CanvasElement -> Aff Blob -toBlob' (Lossless (MediaType format)) = toAffE <<< runFn2 toBlobFormat format -toBlob' (Lossy (MediaType format) quality) = toAffE <<< runFn3 toBlobFormatQuality format quality +toBlob' (Lossless (MediaType format)) = blobCBToAff <<< runFn2 toBlobFormat format +toBlob' (Lossy (MediaType format) quality) = blobCBToAff <<< runFn3 toBlobFormatQuality format quality foreign import drawImage :: Context2D -> CanvasImageSource -> Number -> Number -> Effect Unit diff --git a/src/Graphics/Canvas/Offscreen.purs b/src/Graphics/Canvas/Offscreen.purs index c6de34e..f5c627b 100644 --- a/src/Graphics/Canvas/Offscreen.purs +++ b/src/Graphics/Canvas/Offscreen.purs @@ -23,13 +23,13 @@ import Control.Promise (Promise, toAffE) -- | in the document and will not be rendered. foreign import data OffscreenCanvas :: Type --- | Creates a virtual canvas with the given width and height. +-- | Create a virtual canvas with the given width and height. foreign import createOffscreenCanvas :: { width :: Int, height :: Int } -> (Effect OffscreenCanvas) --- | Gets the logical height in pixels of the virtual canvas. +-- | Get the logical height in pixels of the virtual canvas. foreign import getHeight :: OffscreenCanvas -> Effect Int --- | Gets the logical width in pixels of the virtual canvas. +-- | Get the logical width in pixels of the virtual canvas. foreign import getWidth :: OffscreenCanvas -> Effect Int foreign import toBlobDefault :: OffscreenCanvas -> Effect (Promise Blob) From bb9b265bb6b1b0cfde34f68ec1171eee4ba5e347 Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Mon, 17 Mar 2025 13:42:28 -0400 Subject: [PATCH 12/14] more general type because why not --- src/Graphics/Canvas.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graphics/Canvas.purs b/src/Graphics/Canvas.purs index 5159dec..4c578d7 100644 --- a/src/Graphics/Canvas.purs +++ b/src/Graphics/Canvas.purs @@ -676,7 +676,7 @@ blobJPEG :: Number -> BlobFormat blobJPEG = Lossy imageJPEG -- Thanks to mikesol for letting me know about makeAff! -blobCBToAff :: ((Blob -> Effect Unit) -> Effect Unit) -> Aff Blob +blobCBToAff :: forall a. ((a -> Effect Unit) -> Effect Unit) -> Aff a blobCBToAff b = makeAff \cb -> b (cb <<< Right) $> mempty foreign import toBlobDefault :: CanvasElement -> (Blob -> Effect Unit) -> Effect Unit From 8312f03c28e142bf9b1db6355eb841c1110840bd Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Tue, 18 Mar 2025 09:17:01 -0400 Subject: [PATCH 13/14] changelog + remembered w/h are mutable --- CHANGELOG.md | 2 ++ src/Graphics/Canvas/Offscreen.js | 12 ++++++++++++ src/Graphics/Canvas/Offscreen.purs | 13 +++++++++++++ 3 files changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 522e284..9279848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ Breaking changes: New features: - Added `imageSmoothingEnabled` to toggle the image smoothing of a `Context2D`. +- Added `OffscreenCanvas` and its interface. +- Added `toBlob`, `toBlob'`, and a helper `BlobFormat` type for the arguments to `toBlob'`. Bugfixes: diff --git a/src/Graphics/Canvas/Offscreen.js b/src/Graphics/Canvas/Offscreen.js index 53fe10b..b91f4b3 100644 --- a/src/Graphics/Canvas/Offscreen.js +++ b/src/Graphics/Canvas/Offscreen.js @@ -16,6 +16,18 @@ export function getWidth(offscreen) { }; } +export function setHeightImpl(offscreen, h) { + return function() { + offscreen.height = h; + }; +} + +export function setWidthImpl(offscreen, w) { + return function() { + offscreen.width = w; + }; +} + export function toBlobDefault(offscreen) { return function() { return offscreen.toBlob(); diff --git a/src/Graphics/Canvas/Offscreen.purs b/src/Graphics/Canvas/Offscreen.purs index f5c627b..6a2fa52 100644 --- a/src/Graphics/Canvas/Offscreen.purs +++ b/src/Graphics/Canvas/Offscreen.purs @@ -3,6 +3,8 @@ module Graphics.Canvas.Offscreen , createOffscreenCanvas , getHeight , getWidth + , setHeight + , setWidth , toBlob , toBlob' , getContext2D @@ -32,6 +34,17 @@ foreign import getHeight :: OffscreenCanvas -> Effect Int -- | Get the logical width in pixels of the virtual canvas. foreign import getWidth :: OffscreenCanvas -> Effect Int +foreign import setHeightImpl :: Fn2 OffscreenCanvas Int (Effect Unit) +foreign import setWidthImpl :: Fn2 OffscreenCanvas Int (Effect Unit) + +-- | Set the logical height in pixels of the virtual canvas. +setHeight :: OffscreenCanvas -> Int -> Effect Unit +setHeight = runFn2 setHeightImpl + +-- | Set the logical width in pixels of the virtual canvas. +setWidth :: OffscreenCanvas -> Int -> Effect Unit +setWidth = runFn2 setWidthImpl + foreign import toBlobDefault :: OffscreenCanvas -> Effect (Promise Blob) -- | Create a `Blob` of the image data on the virtual canvas, as a PNG file. From c883bd261635c99e0620d82c05561a07e4d03a88 Mon Sep 17 00:00:00 2001 From: UnrelatedString Date: Tue, 18 Mar 2025 09:34:52 -0400 Subject: [PATCH 14/14] better placeholder comment for ImageBitmap --- src/Graphics/Canvas/Offscreen.purs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Graphics/Canvas/Offscreen.purs b/src/Graphics/Canvas/Offscreen.purs index 6a2fa52..8448659 100644 --- a/src/Graphics/Canvas/Offscreen.purs +++ b/src/Graphics/Canvas/Offscreen.purs @@ -65,4 +65,4 @@ toBlob' (Lossy (MediaType format) quality) = toAffE <<< runFn3 toBlobFormatQuali -- | discrepancies in functionality. foreign import getContext2D :: OffscreenCanvas -> Effect Context2D --- TODO: focus long enough to figure out the image bitmap thing +-- transferToImageBitmap not added because ImageBitmap seems like a very large can of worms!