4

I have a KineticJS 5.1.0 stage on an iPhone 5. Due to the screen size of the iPhone my stage is bounded by 320px x 320px. The image I capture is 1280px x 1280px. I want to be able to process this image on the stage without loosing quality due to shrinking.

Here is the original image (Big version: https://dl.dropboxusercontent.com/u/47067729/originalImage2.jpg):

enter image description here

Here is what happens, when I load the image into the stage:

enter image description here

The image looks very strange it has a lot of strips inside as you can see on the image.

When I apply a filter the image looks like this.

enter image description here

Which is not what it should look like.

I created a JsFiddle (http://jsfiddle.net/confile/qhm7dn09/) which shows this problem

console.clear();


var imageObj = new Image();
var img = null;
var saveBtn = document.getElementById("save");
var resultImage = document.getElementById("resultImage");
var original = document.getElementById("original");
var downloadLink = document.getElementById("DownloadLink");

Kinetic.pixelRatio = 4;
stage = new Kinetic.Stage({
        container: 'container',
        width: 320,
        height: 320
    });
layer = new Kinetic.Layer();

img = new Kinetic.Image({
    x: 0,   
    y: 0,   
    image: new Image()
});

layer.add(img);
stage.add(layer);

imageObj.onload = function() {

    var tmpWidth = Math.floor(imageObj.width /2);
    var w = Math.min(tmpWidth, 320);
    var h = imageObj.height * w / imageObj.width;

    img.setImage(this);
    img.setWidth(w);
    img.setHeight(h);

    img.cache(); // 5.1.0

  //  img.setFilter(Kinetic.Filters.Shinny);  // 4.5.2.
    img.filters([Kinetic.Filters.Shinny]); // 5.1.0
    layer.draw();
};

imageObj.crossOrigin = "anonymous";
var imageUrl = "https://dl.dropboxusercontent.com/u/47067729/originalImage2.jpg";
imageObj.src = imageUrl;      
original.src = imageUrl;

saveBtn.onclick = function(evt) {
   console.log("save");
   var canvas = $(stage.getContent()).find('canvas').get(0);

    console.log("canvas: "+canvas);
    var dataURL = canvas.toDataURL('image/jpeg', 1.0);
    resultImage.src = dataURL;
    downloadLink.href = dataURL;

};

How can I edit an image which is much bigger than my screen/stage size without loosing quality?

Edit: The stage/canvas which is present to the user is still some kind of preview. The original image should not be downscaled. After applying effects and exporting the stage I want to have to exported image to be of the same size as the original one without quality reduction. I do not want to resize a 320px x 320px stage back to 1280px x 1280px which would cause a hugh quality loss.

Michael
  • 32,527
  • 49
  • 210
  • 370
  • The fiddle doesn't work. Have you tried to reduce the pixelRatio setting? –  Nov 19 '14 at 06:20

1 Answers1

4

The ringing pattern seen on iPhone is a resizing artifacts, a kind of Moiré pattern. It is actually pretty common.

As of writing, we have no control on how canvas scale our images, so we need to work around it:

  1. Maunally create one or more scaled images using a more suitable scaling algorithm. The aim is to avoid scaling, and thus avoid any artifacts.

  2. Implement a step down algorithm, and produce an image of more suitable dimension before handing it to Kinetic.

  3. Blurring the image before downscaling will tone down the artifacts, but will not get rid of it. In Kinetic just apply the Blur filter normally.

  4. Implement a more suitable scaling algorithm, and use it to dynamically scale image to dimension (i.e. an automated #1).

Naturally, the first two approachs are recommanded.


A 320px stage will only export a 320px image. To export the original image in its original size without scaling, you need to export from a big enough stage. The new stage does not need to be visible; this is off-screen rendering.

I have modified the fiddle's save handler to demonstrate it: http://jsfiddle.net/qhm7dn09/13/embedded/result

Keep in mind that JavaScript is nowhere as fast or as efficient as native programs, and most mobile will have a hard time processing big image in a browser using JS.


The original question asked how to improve downscaled image quality.

Given the stock blur filter, it is relatively easy to implement a sharpen filter for Kinetic 5.1. Specifically, an unsharp mask filter.

Kinetic.Filters.UnsharpMask = function kinetic_filter_unsharpmask( imageData ) {
    var amount = this.attrs.unsharpAmout / 100; // Normally 1 to 100 (%)
    var radius = Math.round( this.attrs.unsharpRadius );
    var threshold = Math.round( this.attrs.unsharpThreshold ); // 1 to 765
    if ( amount && radius > 0 && threshold > 0 ) {
        var data = imageData.data, len = data.length, i;
        // Copy the image and call blur filter on it
        var blurData = new Uint8Array( imageData.data.buffer.slice(0) );
        Kinetic.Filters.Blur.call( 
            { blurRadius: function(){ return radius; } },
            { data: blurData, width: imageData.width, height: imageData.height } );
        // Unsharp mask
        for ( i = 0 ; i < len ; i += 4 ) {
            // Calculate difference
            var d = [ data[i] - blurData[i], data[i+1] - blurData[i+1], data[i+2] - blurData[i+2] ];
            var diff = Math.abs( d[0] ) + Math.abs( d[1] ) + Math.abs( d[2] );
            // Apply difference
            if ( diff && diff >= threshold ) {
                data[i  ] = Math.max( 0, Math.min( Math.round( data[i  ] + d[0] * amount ), 255 ) );
                data[i+1] = Math.max( 0, Math.min( Math.round( data[i+1] + d[1] * amount ), 255 ) );
                data[i+2] = Math.max( 0, Math.min( Math.round( data[i+2] + d[2] * amount ), 255 ) );
            }
        }
    }
}

Usage:

img = new Kinetic.Image({
    x: 0, y: 0, image: imageObj,
    unsharpRadius: 3, // Pixel range, should be integer (blur filter will round it to integer)
    unsharpAmout: 50, // 1 to 100
    unsharpThreshold: 10 // 1 to 765
});
img.filters([Kinetic.Filters.UnsharpMask]);

The original question also asked about perceived differences in rendered image, which is unlikely to be caused by different library.

From experience, these factors commonly affect the appearance of displayed colour:

  1. Different monitors (even same model), or same monitor with different settings or modes, will display colours differently.
  2. Screen adjustment software, such as Adobe Gamma or some display card drivers, can considerably distort colours.
  3. Different view angle; same image can look different on the left and on the right, or up and down.
Community
  • 1
  • 1
Sheepy
  • 17,324
  • 4
  • 45
  • 69
  • I updated my question. Could you please have a look? – Michael Nov 20 '14 at 14:42
  • @confile Hmm. It is actually a new question, with quite different answers from the original question. Fortunately it is not exactly new, so I have updated the answer to point you to relevant questions and code, please have a look. – Sheepy Nov 20 '14 at 16:02
  • Please see my edit: The stage/canvas which is present to the user is still some kind of preview. The original image should not be downscaled. After applying effects and exporting the stage I want to have to exported image to be of the same size as the original one without quality reduction. I do not want to resize a 320px x 320px stage back to 1280px x 1280px which would cause a hugh quality loss. – Michael Nov 20 '14 at 16:44
  • Updated answer to preserve export dimension. Fiddle: http://jsfiddle.net/qhm7dn09/12/ – Sheepy Nov 21 '14 at 03:13
  • I tested your fiddle on iPhone. The preview image still has the bad quality and the stribes. When I click on save nothing happens. – Michael Nov 21 '14 at 08:36
  • @confile Ah. I couldn't directly run the original fiddle either and I thought you worked around it. I have change the image to data uri so that the fiddle can run, both on desktop and on my android. I cannot help more with iPhone rings since I do not use Apple and all six browsers I tested does not show the observed pattern. – Sheepy Nov 21 '14 at 13:58