1

I have a specific case, but I'm also curious in general.

I have a canvas, I get it's ImgData using createImageData, and I set it's data property to be something else, in order to then use PutImageData and pass it this modified ImgData.

My PureScript version fails, although writing the logic in Javascript in the console works flawlessly. The error is:

foreign.js:423 Uncaught TypeError: Failed to execute 'putImageData' on 'CanvasRenderingContext2D': 
parameter 1 is not of type 'ImageData'.

In PureScript:

getCleanBuffer :: forall e. Context2D -> Eff (canvas :: Canvas | e) ImageData
getCleanBuffer ctx = do
  imgData <- getImageData ctx 0.0 0.0 160.0 144.0
  return imgData { data = buffer }
 where buffer = asUint8ClampedArray $ A.replicate (160*144*4) 0

Writing this javascript in console works (arr is 160*144*4 zeroes)

var arr2 = new Uint8ClampedArray(arr);
var imgData = ctx.getImageData(0,0,160,144);
imgData.data = arr2;
ctx.putImageData(imgData,0,0);

I've debugged getCleanBuffer and here's what I found

var getCleanBuffer = function (ctx) {
var buffer = Data_TypedArray.asUint8ClampedArray(Data_Array.replicate((160 * 144 | 0) * 4 | 0)(0));
return function __do() {
    var v = Graphics_Canvas.getImageData(ctx)(0.0)(0.0)(160.0)(144.0)();
    var $9 = {};
    for (var $10 in v) {
        if (v.hasOwnProperty($10)) {
            $9[$10] = v[$10];
        };
    };
    $9.data = buffer;
    return $9;

This $9 (ImgData copy with modified data) variable, when inspected, is revealed not to be of type ImgData, like v (the original ImgData) was, but it's just an 'Object'. It's missing the width and height which v had, but even when I fix it during debug I still get the same error.

I think the problem is that the new object is a typeless record. The fact that the width and height were not copied is another problem.

I can write a javascript function to set the data attribute manually. But this case in general, seems like a common one, surely I'm missing something, am I not?

Thanks!

PsyFish
  • 287
  • 1
  • 9
  • This seems like a bug in the canvas library. Could you please file an issue there? – Phil Freeman May 11 '16 at 21:24
  • At first I thought you mean a bug in HTML5's canvas. After seeing the bug report gb. opened, I understood. But what should ImgData be then? Not precisely, just in general. – PsyFish May 12 '16 at 04:47

2 Answers2

3

In this case the type of ImageData is incorrect, it's a type synonym for a record, but it is not safe to treat JavaScript class typed values as records for exactly the reason you're discovering here.

I've opened a bug report as Phil suggested.

For the general case, this is where lenses come in handy. You'd define a lens like _data :: LensP ImageData Uint8ClampedArray, and then could do imgData # _data %~ buffer to achieve something similar to the update syntax.

gb.
  • 4,629
  • 1
  • 20
  • 19
  • 1
    Thanks for lens explanation. My general question was not about how to set something so much as how to set a property of a class typed value without breaking it. But Now I understand the problem was with how the ImgData type was defined. But like I asked Phil in the comments, what should types like ImgData should be defined as? Simply foreign data imports? With specialized set functions? – PsyFish May 12 '16 at 04:49
  • Ah ok, sorry I didn't catch that. Yeah, the idea would be to provide the type as `foreign data` and then `getData` / `setData` foreign imported functions to manipulate the object (with `setData` either copying it in the appropriate manner, or operating in `Eff` and mutating it directly depending on what makes more sense for the interface). – gb. May 12 '16 at 13:51
0

I don't know purescript at all, so I won't answer the title of your question, but for your precise case, this is not how you should create a "clean ImageData".

Different browsers have different implementations of the ImageData Object and since specs only required a "TypedArray" as the data property, you can't be sure that the type of this buffer should be an Uint8ClampedArray in every UA (related Q/A). (now living specs do specify the type of this TypedArray as Uint8ClampedArray).

But fortunately enough, it seems that you are trying to reinvent the wheel, when the canvas API already has a context2D.createImageData(width, height) method, or a check-supported-browsers ImageData(Uint8ClampedArray) Constructor if your are in worker (where createImageDatais not available)

So just use it, it will work in any UA supporting the canvas API, and you won't have to worry about copying any type.

Community
  • 1
  • 1
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Thanks for your answer Kaiido. Although it can't be seen in the examples I've put above, I do use createImageData(), set it's data property, then use putImageData() over it. So it seems to me like it fits with what you recommend – PsyFish May 12 '16 at 04:51
  • @PsyFish, then what's the purpose of `getCleanBuffer` ? You should not change the `data` property as you are doing. – Kaiido May 12 '16 at 04:53
  • I might have misunderstood you, sorry. You meant that getCleanBuffer should've used createImageData rather than getImageData? Sorry I confused these 2 functions. Is it ok to set the data property of an ImgData received through getImageData? – PsyFish May 12 '16 at 05:01
  • @PsyFish, I would say that some UA may accept it but not every one. Some have a `set()` method on the `data` property. Some don't. But anyway, if what you want is to get a new clear imageData, only filled with zeroes, (or black-transparent pixels), then call `ctx.createImageData()`, don't try to do magics ;-) – Kaiido May 12 '16 at 05:06