简体   繁体   中英

Perspective transformations of images/textures with HTML5 Canvas

I have used the 'keystoning' technique described here to successfully add perspective to a turning 'hard cover' book page with html5 canvas.

What essentially happens is an image/canvas element is defined as the source texture. during each render loop this is split up into segments of a defined width (1px being the best quality) and each segment is has its height scaled dependent upon its position on the X axis of the texture.

This creates a good illusion of perspective with a given source image/texture as can be seen below:

在此输入图像描述

This works fine for the 'hard cover' page turns, the texture along with any included text gives a great impression of perspective. However I need to apply the same kind of keystoning to the 'soft' page turns. The problem is that a simple perspective transformation will not work as the pages themselves are defined with curves as can be seen below:

在此输入图像描述

Currently the page texture is scaled to the maximum height of the curve at any given point in the page turn with the page edge quadratic curve paths set to clip the image/texture. Since I draw the shadows and page lines dynamically using various standard canvas function this looks acceptable as the pagelines (drawn with quadratic curves) provide a natural perspective to the page turn.

However the text itself (which is sourced from another cached canvas/image element) does not look good enough, remaining totally flat at all times.

What I would like to do is somewhow apply the same slicing/segmenting/scaling keystone technique as mentioned above but somehow calculate the heights of each 1px segment based upon the quadratic curves (drawn as ctx.quadraticCurveTo(); ) path and vertical position within the canvas.

In my example image it actually doesn't look too bad, however when text is nearing the top/bottom of the page the warping effect should of course be greater. Not only that but we also need to calculate a horizontal scale factor to squash the text closest to the page fold.

I'm not really in a position to provide any example code I'm afraid. but essentially we are doing things very similarly to the way key-stoning is described in the link I have provided above. And to summarize I need to be able to use the quadratic curve coordinates/values to calculate the scale factor of each segment in the slicing/rendering function show below:

function keystoneAndDisplayImage(ctx, img, x, y, pixelWidth, scalingFactor) {
        var h = img.height,
             w = img.width,

            // The number of slices to draw.
            numSlices = Math.abs(pixelWidth),

            // The width of each source slice.
            sliceWidth = w / numSlices,

            // Whether to draw the slices in reverse order or not.
            polarity = (pixelWidth > 0) ? 1 : -1,

            // How much should we scale the width of the slice 
            // before drawing?
            widthScale = Math.abs(pixelWidth) / w,

            // How much should we scale the height of the slice 
            // before drawing? 
            heightScale = (1 - scalingFactor) / numSlices;

            for(var n = 0; n < numSlices; n++) {

            // Source: where to take the slice from.
            var sx = sliceWidth * n,
                sy = 0,
                sWidth = sliceWidth,
                sHeight = h;

            // Destination: where to draw the slice to 
            // (the transformation happens here).
            var dx = x + (sliceWidth * n * widthScale * polarity),
                dy = y + ((h * heightScale * n) / 2),
                dWidth = sliceWidth * widthScale,
                dHeight = h * (1 - (heightScale * n));

            ctx.drawImage(img, sx, sy, sWidth, sHeight, 
                          dx, dy, dWidth, dHeight);
        }
    }

... But I really have no idea where to begin. Any help would be greatly appreciated.

EDIT:

Having thought a little more about what I am hoping to achieve, I have realized that a perfect representation of 3d texture mapping via this method may be a little too much to ask. Therefore I would be more than happy with any answers which are able to simply alter the height scale and y position of each segment based on a quadratic curve defined in html5 canvas.

Here is a better example image:

在此输入图像描述

As you can see the larger text stays completely straight with a single non-keystoned image texture when I need the 1px width segments to be calculated by the curve factor and its y position in order for it to naturally follow the page turn. Failing that any other 'hack' or method to achieve a semi-realistic perspective effect in this instance would be very helpful.

I have found the solution finally....

Thanks to the fantastic answer here: Center point on html quadratic curve

I had the equations necessary to get the y value of any point on the quadratic curve.

Then it was a simple case of using this y value to calculate an adjustment to the height of the text texture. I have some tweaking to do to find the best quadratic curve calculation but the result is pretty fantastic even as is.

See the image below:

在此输入图像描述

It currently runs at approx 60fps in Firefox/Chrome and around 45fps in IE9/10. I'm sure there is room for optimization but regardless I am very pleased with the result. Obviously it does not horizontally stretch/squash the texture in line with the curve but doing so would require at least one extra pass through the texure, probably more, which would cripple performance.

The other option was to resort to actual Affine 3d texture mapping, but in my attempts I found this method to be far superior in terms of performance and quality while obviously sacrificing a little accuracy.

The loop, which is within my main render loop for the page flip looks like this:

    for (var i = 0; i < segments; i++) {
            var sw = i >= segments - 1 ? segmentWidth : segmentWidth + 3;
            var sourceLeft = texw * ((i * segmentWidth) / texw);
            var sourceWidth = texw * (sw / texw)
            var texleft = foldX - foldWidth + (i * segmentWidth);                   
            var percent = ((i * segmentWidth)/foldWidth);
            var curve = self.getQuadraticCurvePoint(foldX - foldWidth, 0, foldX, -verticalOutdent * 2, foldX, 0, percent)
            var curvedheight = self.PAGE_HEIGHT + Math.abs(curve.y*2);
            var y = -((curvedheight - self.PAGE_HEIGHT)/2);

context.drawImage(self.flips[flip.index+1].leftcanvas, sourceLeft, 0, sourceWidth, texh, texleft, y, sw, curvedheight);

    }

With the related quadratic point functions being as follows:

    getQuadraticCurvePoint : function(startX, startY, cpX, cpY, endX, endY, position) {
            return {
                x:  this.getQBezierValue(position, startX, cpX, endX),
                y:  this.getQBezierValue(position, startY, cpY, endY)
            };
        },

and:

getQBezierValue : function(t, p1, p2, p3) {
                var iT = 1 - t;
                return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
        },

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM