foo123/FILTER.js

Apply filter for a part

Closed this issue · 14 comments

@foo123
Hi Mr foo123,
According to your API References, I use .select() to select only one part to apply twirl filter. But it didn't work. When debugging, console.log(canvas.select(0, 0, 100, 100)) imageData is always null.

This is your way: Work on IE9 but didn't work when select a part.

var $F = FILTER,
            canvas;
        var disposeWorker = function () {
            this.worker(false);
        };

         // it is faster to instantiate the twirl filter here once
         // and just adjust parameters inside the loop
        var twirl = new $F.GeometricMapFilter().twirl(1, 1, 0.5, 0.5);
        var step = 10;
        var img = new $F.Image("images/apple.jpg", function () {
            canvas = img.clone();
            document.body.appendChild(canvas.canvasElement);
            setInterval(function () {
                canvas.setImage(img.canvasElement);
                canvas.select(0, 0, 100, 100);
                twirl.angle = step;
                twirl.radius = img.height;
                twirl.apply(canvas);
                step -= 1;
            }, 1000);
        });

This is my way: Works when I choose the part of image is x1: 0, y1: 0, width: 100, height: 100. But it didn't work on IE9.

var offset = {
  x: 0,
  y: 0,
  w: 100,
  h: 100
};
var canvas = document.createElement("canvas"),
            canvas2 = document.createElement("canvas"),
            context = canvas.getContext("2d"),
            context2 = canvas2.getContext("2d");
                        img = new Image();
            img.src = "images/apple.jpg";
            img.onload = function () {
                canvas.width = img.width;
                canvas.height = img.height;

                var step = 10;
                var twirl = setInterval(function () {
                        step -= 1;
                        context.drawImage(img, 0, 0);
                        var selection = context.getImageData(offset.x, offset.y, offset.w, offset.h);
                        canvas2.width = offset.w;
                        canvas2.height = offset.h;
                        context2.putImageData(selection, 0, 0);

                        var f = new FILTER.Image();
                        f.setImage(canvas2);
                        var twirl = new FILTER.GeometricMapFilter().twirl(step, canvas2.width < canvas2.height ? canvas2.width : canvas2.height, 0.5, 0.5);
                        twirl.apply(f);
                        context.putImageData(f.imageData, offset.x, offset.y);
                        document.body.appendChild(canvas);
                }, 1000);
            };

Thank you in advance.

The select method takes relative percentages (i.e between 0 and 1 included) as arguments and not absolute values or values outside this region.

Examples

image.select(0,0,1,1); // select whole image
image.select(0,0,0.5,1); // select left half image
image.select(0.5,0,1,1); // select right half image
image.select(0,0,1,0.5); // select top half image
image.select(0,0.5,1,1); // select bottom half image

// etc..

// these would give wrong results in various browsers
image.select(0,0,-1,1); // negative numbers
image.select(0,0,0.5,1.5); // outside of [0,1] range
// etc..

The image.imageData property, is not immediately alterted when a selection is made (it uses lazy initialisation). Also when a selection is made there is another property called image.imageDataSel which is also lazy initialised. (which eans that you probably going to get a null value if checked immediately)

Havent seen any issues with IE9+ for this feature (per correct application)

Note image.select can be done only once and it is persistent (no need to re-select every time).

If you want to deselct there is the method image.deselect() (which disacrds any previous selection).

So th example could be sth like:

var img = new $F.Image("images/apple.jpg", function () {
            canvas = img.clone().select(0,0,1,1); // select only once here
            document.body.appendChild(canvas.canvasElement);
            setInterval(function () {
                canvas.setImage(img.canvasElement);
               // this is not needed, ecept if selection changes, which is not the case now
                //  canvas.select(0, 0, 100, 100);
                twirl.angle = step;
                twirl.radius = img.height;
                twirl.apply(canvas);
                step -= 1;
            }, 1000);
        });

Yes, I didn't know about the range of select() arguments. What could I do if I want to apply filter to a image part from coordinate (100, 100) and has width: 200, height: 200?

Ok, yeap, the select method initially used absolute coordinates, but using relative is easier for various reasons (for example one can select half the image, even when the image is not fully loaded so the width and height are not known beforehand, etc..)

If you have the absolute coordinates you can use sth like this (note in order to change selection deselect is not needed , deselect is needed only if one wants to discard any previous set selection )

// assume the absolute coordinates are x1, y1, x2, y2 and w is image width and h is image height
// (x1,y1) is top-left corner of selection
// (x2,y2) is bottom-right corner of selection
image.select(x1/w, y1/h, x2/w, y2/h);

Thank you so much. It worked.

Oh, It still didn't work on IE9
This is my demo: http://xkcn.didaubaygio.net/filterjs/

How can I resolve it? Mr @foo123

Unfortunately i have upgraded to IE11, so i cant test it with IE9 (and IETester is no exact simulation)

if you have any js errors that IE9 throws, you can post them here or open a new issue.

The demo works for me both in Firefox and IE11

Btw it is possible this is an issue with Jcrop plugin and not with filter.js, but anyway post any js error messages you get.

IE9 used to work, but not supporting TypedArrays in canvas filter.js uses a workaround to copy data in images fast, it is possible this can be an issue as well, although i remember that this worked on IE9 (when i had tested it on IE9)

When I debug with IE9, it didn't throw any error to console but I saw canvas still not applied effect yet. And the imageData is null.
error

Yes it is probably with the custom PixelArray.data.set method added by filter.js for browsers who dont support imageData.set method

This used to work on IE9 (as it was implemented mostly for IE), yet filter.js has made a couple of updates since then and it may have an issue in IE (prior to 10 or 11)

i dont think this is going to be fixed as IE (which certainly has to optimize itself) is already in version 11.

If you want you can search previous versions of filter.js (especially the core/filter.js file) (for example 4-5 versions back) and replace the custom set method used there in the current version of filter.js.

Note filter.js is going to be updated to a new version soon (i am working on it), with new pugins, methods etc.. so if you do this, beware that it is not going to be compatible with the current or next versions

Thank you Mr @foo123
Btw, could you tell me which lines of that method? Because I really need to make Twirl effect run on IE9. And I only use Twirl effect so I think I don't need to use the new version.

Thank you in advance.

i think in version/upload 0.6.3 (and previious in repository) this workaround was made for IE and other browsers not supporting imageData.set method

In /core/Filter.js do the following updates:

// add this at some line after the "notSupportClamp" var is set
FILTER.notSupportTyped = notSupportClamp && (typeof CanvasPixelArray !== "undefined");

In /core/Image.js (inside the Image.prototype) do the following updates:

        // set direct data array
        setData : function(a) {
           if (FILTER.notSupportTyped) this._setData(a);
            else this.imageData.data.set(a); // not supported in Opera, IE, Safari
            this.context.putImageData(this.imageData, 0, 0); 
            this.imageData=this.context.getImageData(0, 0, this.width, this.height);
            this._histogramRefresh=true;
            this._integralRefresh=true;
            return this;
        },

         _setData : function(a) {
            var data = this.imageData.data, l=a.length, i=0, t;
            while (i<l) { data[i]=a[i]; i++; }
        }

This was the workaround that worked if i remember correctly (this is in version 0.6.3 and commented out) But if you do the updates it should work

Note This will not support image.select() for 2 reasons, first image.select was added in next versions, and second image.select needs a custom setData to set only the data inside the selection and not all the data. If you want to make to changes to support also image selections, it should work

All in all what is needed is that the _setData method either sets the data in a rectangle of the original data (selection) or same method is called in imageDataSel instead of imageData property (i think second option is easier but first option is also valid)

PS. IE is already in new versions, i still suggest you support newest version of IE or enabling your users to update to latest IE version, anyway.

Hope this helps

Hi Mr @foo123
If I make that updates, I need to rebuild the source. But I couldn't find the buildtools on https://github.com/foo123/scripts/buildtools. Could you guide me how to build the source?

Thanks in advance.

I made changes directly in build/filter.js. But it still didn't work on IE9. I used the old version but the result is't good as I want.
Demo

Yeap sorry the buildtools are here https://github.com/foo123/scripts/tree/master/buildtools (but it is better to copy the whole repository and place it in a folder where you can access it locally)

The whole idea about the IE9 workaround is that, because it does not support the native imageData.data.set method, one has to simulate it, by copying the data one-by-one from a given array into the imageData.data array (this is what the _setData method was doing), but this ican be slow especially in an operation that takes place continously in a loop or video.

So to fix this for IE9 you should handle special case in Image.setData and Image.setSelectedData methods to use the simulation copy (one-by-one element) in case this is IE9 (or more generally the browser does not support the imageData.data.set native method).

The example i posted was from FILTER.js 0.6.3 (for illustration purposes) but up to the current version many more changes were made, so to solve the issue do what is described in the previous paragraphs.

Yeah, thank you. I got it.