Author Topic: Rotozoom with bilinear interpolation  (Read 2135 times)

0 Members and 1 Guest are viewing this topic.

Offline _vince

  • Seasoned Forum Regular
  • Posts: 422
Rotozoom with bilinear interpolation
« on: May 02, 2020, 11:41:33 pm »
I've recently used this algorithm in the shapes demo and wanted to try it on an image, it is supposed to improve the appearance of a rotated image. This code is inefficient because it calculates many unnecessary pixels, I may fix this later.

Code: [Select]
deflng a-z

'const sw = 800
'const sh = 600

dim shared pi as double
pi = 4*atn(1)

img = _loadimage("leopardx.jpg", 32)
w = _width(img)
h = _height(img)

dim zoom as double
dim a as double
zoom = 2.5

a = 2*sqr(w*w/4 + h*h/4)*zoom

if h < a then h = a

screen _newimage(w + a*2, h, 32)

_putimage (0,0), img

dim rot as double
do
        rot = rot + 0.1

        line (w, 0)-step(a*2, a),_rgb(0,0,0),bf
        rotzoom img, w + a/2, a/2, rot, zoom
        rotzoomb img, w + a + a/2, a/2, rot, zoom

        _display
        _limit 30
loop until _keyhit = 27

sleep
system

sub rotzoomb(img, x0, y0, rot as double, zoom as double)
        dim a as double
        dim xx as double, yy as double
        dim dx as double, dy as double

        w = _width(img)
        h = _height(img)

        if zoom = 0 then zoom = 1
        a = 2*sqr(w*w/4 + h*h/4)*zoom

        _source img

        for y=0 to a
        for x=0 to a
                xx = (x - a/2)*cos(rot)/zoom - (y - a/2)*sin(rot)/zoom + w/2
                yy = (x - a/2)*sin(rot)/zoom + (y - a/2)*cos(rot)/zoom + h/2

                if (int(xx) >=0 and int(xx) < w - 1 and int(yy) >= 0 and int(yy) < h - 1) then
                        tl = point(int(xx), int(yy))
                        tr = point(int(xx) + 1, int(yy))
                        bl = point(int(xx), int(yy) + 1)
                        br = point(int(xx) + 1, int(yy) + 1)

                        dx = xx - int(xx)
                        dy = yy - int(yy)

                        r = _round((1 - dy)*((1 - dx)*  _red(tl) + dx*  _red(tr)) + dy*((1 - dx)*  _red(bl) + dx*  _red(br)))
                        g = _round((1 - dy)*((1 - dx)*_green(tl) + dx*_green(tr)) + dy*((1 - dx)*_green(bl) + dx*_green(br)))
                        b = _round((1 - dy)*((1 - dx)* _blue(tl) + dx* _blue(tr)) + dy*((1 - dx)* _blue(bl) + dx* _blue(br)))

                        pset (x0 - a/2 + x, y0 - a/2 + y), _rgb(r, g, b)

                elseif (int(xx) >=0 and int(xx) < w - 1 and int(yy) >= 0 and int(yy) < h - 1) then
                        pset (x0 - a/2 + x, y0 - a/2 + y), point(int(xx), int(yy))
                end if
        next
        next
end sub


sub rotzoom(img, x0, y0, rot as double, zoom as double)
        dim a as double

        w = _width(img)
        h = _height(img)

        if zoom = 0 then zoom = 1
        a = 2*sqr(w*w/4 + h*h/4)*zoom

        _source img

        for y=0 to a
        for x=0 to a
                xx = (x - a/2)*cos(rot)/zoom - (y - a/2)*sin(rot)/zoom + w/2
                yy = (x - a/2)*sin(rot)/zoom + (y - a/2)*cos(rot)/zoom + h/2

                if ((xx) >= 0 and (xx) < w and (yy) >=0 and (yy) < h) then
                        pset (x0 - a/2 + x, y0 - a/2 + y), point(int(xx), int(yy))
                end if
        next
        next
end sub
qb64rz.png
* qb64rz.png (Filesize: 727.78 KB, Dimensions: 1397x607, Views: 331)
leopardx.jpg
* leopardx.jpg (Filesize: 39.38 KB, Dimensions: 182x161, Views: 348)

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Rotozoom with bilinear interpolation
« Reply #1 on: May 03, 2020, 10:10:20 am »
Maybe you missed it, far more efficient with _MAPTRIANGLE

Here is Roto with independent x and y axis scales:
https://www.qb64.org/forum/index.php?topic=1511.msg115148#msg115148

When scales are both 1 you have 1 to 1 rotation without blur from stretch or shrink.

plug in leopardx.jpg
Code: QB64: [Select]
  1. _TITLE "Another RotoZoom Demo" 'b+ started 2020-03-02  mod with leapoadx.jpg
  2.  
  3. CONST xmax = 1000, ymax = 700, xc = 550, yc = 350
  4. SCREEN _NEWIMAGE(xmax, ymax, 32)
  5. _SCREENMOVE 100, 40
  6. DIM SHARED s&, ao
  7. DIM a, x, y, x1, y1, xs, dxs, ddxs, ys, dys, ddys, maxScale
  8.  
  9. ' Starting from an image I pulled from Internet, I used Paint 3D to give it a transparent background.
  10. s& = _LOADIMAGE("leopardx.jpg")
  11. IF s& = -1 THEN PRINT "No file load... ": SLEEP: END
  12.  
  13. ' Standard Rotation about the image center on a given X, Y location. Rotating image in middle of screen,
  14. ' I used something like this to find ideal angle for level point on left head on right.
  15. WHILE _KEYDOWN(27) = 0
  16.     a = a + _PI(1 / 36)
  17.     IF a > _PI(1.999) THEN a = 0
  18.     CLS
  19.     RotoZoom3 xc, yc, s&, 1, 1, a
  20.     PRINT "Raw image rotation:": PRINT
  21.     PRINT "radian angle:"; a; "   degrees:"; _R2D(a) \ 1; " press key for next angle... esc to rotate on y axis"
  22.     WHILE LEN(INKEY$) = 0: _LIMIT 60: WEND
  23.  
  24. ao = _PI(.27) ' I have to offset the image angle by this amount so that the spike point is on the left
  25. '               and the head is on the right at 0 degrees or radians.
  26.  
  27.  
  28. 'Demo of the independent x and y scale for axis rotations
  29. maxScale = 4: dxs = .01: ddxs = 1: xs = maxScale:
  30.     CLS
  31.     PRINT "Press any for rotation on x axis..."
  32.     RotoZoom3 xc, yc, s&, xs, maxScale, ao
  33.     IF xs + dxs > maxScale OR xs + dxs < -maxScale THEN ddxs = ddxs * -1
  34.     xs = xs + dxs * ddxs
  35.     _DISPLAY
  36.     _LIMIT 60
  37.  
  38. ys = maxScale: dys = .01: ddys = 1
  39.     CLS
  40.     PRINT "Press any to see layout of image over whole screen and end demo..."
  41.     RotoZoom3 xc, yc, s&, maxScale, ys, ao
  42.     IF ys + dys > maxScale OR ys + dys < -maxScale THEN ddys = ddys * -1
  43.     ys = ys + dys * ddys
  44.     _DISPLAY
  45.     _LIMIT 60
  46.  
  47. ' Demo of an applied layout on screen
  48. COLOR , &HFFBBBBBB: CLS ' the image has slight gray halo so hide with gray background
  49. FOR x = 40 TO _WIDTH - 40 STEP 20
  50.     RotoZoom3 x, 15, s&, .2, .2, _PI(1.5 + .27)
  51.     RotoZoom3 x, _HEIGHT - 15, s&, .2, .2, _PI(.5 + .27)
  52. FOR y = 40 TO _HEIGHT - 40 STEP 20
  53.     RotoZoom3 15, y, s&, .2, .2, _PI(1 + .27)
  54.     RotoZoom3 _WIDTH - 15, y, s&, .2, .2, _PI(.27)
  55. FOR a = 0 TO _PI(2) STEP _PI(1 / 6)
  56.     x1 = xc + 200 * COS(a)
  57.     y1 = yc + 200 * SIN(a)
  58.     RotoZoom3 x1, y1, s&, 2, 2, a + ao
  59.  
  60. 'And finally a little show. What is better than a knife thrower throwing bananas?
  61. WHILE _KEYDOWN(27) = 0
  62.     CLS
  63.     drawKite xc, .9 * ymax, 600, a + ao
  64.     _DISPLAY
  65.     _LIMIT 30
  66.     a = a + _PI(2 / 360)
  67.  
  68. SUB drawKite (x, y, s, a)
  69.     RotoZoom3 x, y, s&, s / _WIDTH(s&), s / _HEIGHT(s&), a + ao
  70.     IF s > 10 THEN
  71.         drawKite x + .5 * s * COS(_PI(2) - a), (y - .25 * s) + .25 * s * SIN(_PI(2) - a), s / 1.5, a
  72.         drawKite x + .5 * s * COS(_PI + a), (y - .25 * s) + .25 * s * SIN(_PI + a), s / 1.5, a
  73.     END IF
  74.  
  75. ' Description:
  76. ' Started from a mod of Galleon's in Wiki that both scales and rotates an image.
  77. ' This version scales the x-axis and y-axis independently allowing rotations of image just by changing X or Y Scales
  78. ' making this tightly coded routine a very powerful and versatile image tool.
  79. SUB RotoZoom3 (X AS LONG, Y AS LONG, Image AS LONG, xScale AS SINGLE, yScale AS SINGLE, radianRotation AS SINGLE)
  80.     ' This assumes you have set your drawing location with _DEST or default to screen.
  81.     ' X, Y - is where you want to put the middle of the image
  82.     ' Image - is the handle assigned with _LOADIMAGE
  83.     ' xScale, yScale - are shrinkage < 1 or magnification > 1 on the given axis, 1 just uses image size.
  84.     ' These are multipliers so .5 will create image .5 size on given axis and 2 for twice image size.
  85.     ' radianRotation is the Angle in Radian units to rotate the image
  86.     ' note: Radian units for rotation because it matches angle units of other Basic Trig functions
  87.     '       and saves a little time converting from degree.
  88.     '       Use the _D2R() function if you prefer to work in degree units for angles.
  89.  
  90.     DIM px(3) AS SINGLE: DIM py(3) AS SINGLE ' simple arrays for x, y to hold the 4 corners of image
  91.     DIM W&, H&, sinr!, cosr!, i&, x2&, y2& '   variables for image manipulation
  92.     W& = _WIDTH(Image&): H& = _HEIGHT(Image&)
  93.     px(0) = -W& / 2: py(0) = -H& / 2 'left top corner
  94.     px(1) = -W& / 2: py(1) = H& / 2 ' left bottom corner
  95.     px(2) = W& / 2: py(2) = H& / 2 '  right bottom
  96.     px(3) = W& / 2: py(3) = -H& / 2 ' right top
  97.     sinr! = SIN(-radianRotation): cosr! = COS(-radianRotation) ' rotation helpers
  98.     FOR i& = 0 TO 3 ' calc new point locations with rotation and zoom
  99.         x2& = xScale * (px(i&) * cosr! + sinr! * py(i&)) + X: y2& = yScale * (py(i&) * cosr! - px(i&) * sinr!) + Y
  100.         px(i&) = x2&: py(i&) = y2&
  101.     NEXT
  102.     _MAPTRIANGLE _SEAMLESS(0, 0)-(0, H& - 1)-(W& - 1, H& - 1), Image TO(px(0), py(0))-(px(1), py(1))-(px(2), py(2))
  103.     _MAPTRIANGLE _SEAMLESS(0, 0)-(W& - 1, 0)-(W& - 1, H& - 1), Image TO(px(0), py(0))-(px(3), py(3))-(px(2), py(2))
  104.  
  105.  

 
Another rotozoom demo.PNG





* roto leopardx image.zip (Filesize: 1.49 MB, Downloads: 141)
« Last Edit: May 03, 2020, 10:58:37 am by bplus »

Offline _vince

  • Seasoned Forum Regular
  • Posts: 422
Re: Rotozoom with bilinear interpolation
« Reply #2 on: May 03, 2020, 11:09:44 am »
It's meant to correct the pixely appearance of rotated images, it is slightly apparent in the screenshot if you look closely around the leopard spots, might be hard to see. GL probably has something for that, not sure about maptriangle.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Rotozoom with bilinear interpolation
« Reply #3 on: May 03, 2020, 11:21:55 am »
_MAPTRIANGLE must work with memory image, no way could it run so fast with pixel by pixel rotation which runs smack into math rounding errors.