Put function that does simple transformations

Post your FreeBASIC source, examples, tips and tricks here. Please don’t post code without including an explanation.
Post Reply
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Put function that does simple transformations

Post by counting_pine »

I wrote a function to Put an image (to the screen or another image) that allows simple transformations - e.g. flip, transpose, 90/180° rotation.

It should be about as fast as a simple, general method can get when written in FB. I can't decide whether the method for handling the different transformations is clever or just hackish, but it does the job.

To use it, just call imgput( src, [dest], [x], [y], [transformation]). Transformation can be an IMGPUT_TRANSFORM constant, or if you prefer, a multiple of 90 for simple rotations. If Dest is omitted or 0, then it puts to the screen.

Code: Select all

enum IMGPUT_TRANSFORM
    TRANSFORM_HFLIP      = 1
    TRANSFORM_VFLIP      = 2
    TRANSFORM_D1FLIP     = 4
    TRANSFORM_R90        = TRANSFORM_HFLIP or TRANSFORM_D1FLIP
    TRANSFORM_R180       = TRANSFORM_HFLIP or TRANSFORM_VFLIP
    TRANSFORM_R270       = TRANSFORM_VFLIP or TRANSFORM_D1FLIP
    TRANSFORM_D2FLIP     = TRANSFORM_R180  or TRANSFORM_D1FLIP
    TRANSFORM_NONE       = 0
end enum


sub imgput( _
    byval srcImg as any ptr, _
    byval dstImg as any ptr = 0, _
    byval x as integer = 0, byval y as integer = 0, _
    byval transform as IMGPUT_TRANSFORM = TRANSFORM_NONE)
    
    dim as integer srcWidth, srcHeight, srcBypp, srcPitch
    dim as integer dstWidth, dstHeight, dstBypp, dstPitch
    
    dim as integer srcXLow, srcXUpp, srcYLow, srcYUpp, srcDx, srcDy
    dim as integer dstXLow, dstXUpp, dstYLow, dstYUpp, dstDx, dstDy
    
    dim as const integer ptr srcPX1 = @srcXLow, srcPX2 = @srcXUpp
    dim as const integer ptr srcPY1 = @srcYLow, srcPY2 = @srcYUpp
    
    dim as any ptr srcPData, spr, sp
    dim as any ptr dstPData, dpr, dp
    
    dim as integer screenDest = 0
    
    if imageinfo( srcImg, srcWidth, srcHeight, srcBypp, srcPitch, srcPData ) then exit sub
    if dstImg <> 0 then
        if imageinfo( dstImg, dstWidth, dstHeight, dstBypp, dstPitch, dstPData ) then exit sub
    else
        dstPData = screenptr: if dstPData = 0 then exit sub
        screeninfo( dstWidth, dstHeight, , dstBypp, dstPitch )
        screenDest = 1
    end if
    
    if srcBypp <> dstBypp then exit sub
    
    srcDx = srcBypp: srcDy = srcPitch
    srcXLow = 0: srcXUpp = srcWidth - 1
    srcYLow = 0: srcYUpp = srcHeight - 1
    
    dstDx = dstBypp: dstDy = dstPitch
    
    
    '' set up transform
    if transform and -8 then
        select case transform mod 360
            case 0:         transform = TRANSFORM_NONE
            case 90,  -270: transform = TRANSFORM_R90
            case 180, -180: transform = TRANSFORM_R180
            case 270,  -90: transform = TRANSFORM_R270
        end select
    end if
    if (transform and TRANSFORM_D1FLIP) then
        swap srcDx, srcDy
        swap srcXLow, srcYLow
        swap srcXUpp, srcYUpp
    end if
    if (transform and TRANSFORM_HFLIP) then srcDx = -srcDx: swap srcPX1, srcPX2
    if (transform and TRANSFORM_VFLIP) then srcDy = -srcDy: swap srcPY1, srcPY2
    
    
    '' clipping
    dstXLow = x: dstXUpp = x + srcXUpp - srcXLow
    dstYLow = y: dstYUpp = y + srcYUpp - srcYLow
    
    if dstXLow < 0 then srcXLow -= dstXLow: dstXLow = 0
    if dstYLow < 0 then srcYLow -= dstYLow: dstYLow = 0
    
    if dstXUpp >= dstWidth  then srcXUpp += dstWidth-1  - dstXUpp: dstXUpp = dstWidth-1
    if dstYUpp >= dstHeight then srcYUpp += dstHeight-1 - dstYUpp: dstYUpp = dstHeight-1
    
    
    '' find starting corner addresses
    spr = srcPData + *srcPY1 * abs(srcDy) + *srcPX1 * abs(srcDx)
    dpr = dstPData + dstYLow * abs(dstDy) + dstXLow * abs(dstDx)
    
    
    '' put image
    #macro PUT_TYPE( t )
        if screenDest then screenlock
        for y = srcYLow to srcYUpp
            sp = spr
            dp = dpr
            for x = srcXLow to srcXUpp
                
                *cast( t ptr, dp ) = *cast(t ptr, sp )
                
                sp += srcDx
                dp += dstDx
            next x
            spr += srcDy
            dpr += dstDy
        next y
        if screenDest then screenunlock
    #endmacro
    
    select case SrcBypp
    case 1
        PUT_TYPE( ubyte )
    case 2
        PUT_TYPE( ushort )
    case 4
        PUT_TYPE( uinteger )
    end select
    
end sub


screenres 640, 480, 32

dim as any ptr p = imagecreate(64, 48, RGB(64, 128, 255) )

draw string p, (4, 4), "Test:)"

imgput( p, ,  10, 10      )
imgput( p, ,  84, 10, 90  )
imgput( p, , 168, 10, 180 )
imgput( p, , 252, 10, 270 )

imgput( p, ,  10, 84, TRANSFORM_HFLIP  )
imgput( p, ,  84, 84, TRANSFORM_VFLIP  )
imgput( p, , 168, 84, TRANSFORM_D1FLIP )
imgput( p, , 252, 84, TRANSFORM_D2FLIP )

imagedestroy p

sleep
Sorry, it's quite long. There's a little bit of commenting though, and an example.
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Post by counting_pine »

Update: sorry, I meant to post this a couple of days ago.
I made some changes to the code to improve performance - omitting the target parameter now takes it to a faster, overloaded version of the code. Additionally, if it detects that it's doing a contiguous row copy (i.e. if there's no transformation, or just a vertical flip), then it does a memcpy, rather working pixel by pixel.

So there's even more code this time, but if you can read the simple, non-transforming version, it should make it easier to then follow the transforming version, and see what's been changed.

Code: Select all

#include "crt.bi"

enum IMGPUT_TRANSFORM
    TRANSFORM_HFLIP      = 1
    TRANSFORM_VFLIP      = 2
    TRANSFORM_D1FLIP     = 4
    TRANSFORM_R90        = TRANSFORM_HFLIP or TRANSFORM_D1FLIP
    TRANSFORM_R180       = TRANSFORM_HFLIP or TRANSFORM_VFLIP
    TRANSFORM_R270       = TRANSFORM_VFLIP or TRANSFORM_D1FLIP
    TRANSFORM_D2FLIP     = TRANSFORM_R180  or TRANSFORM_D1FLIP
    TRANSFORM_NONE       = 0
end enum


sub imgput overload( _
    byval srcImg as any ptr, _
    byval dstImg as any ptr = 0, _
    byval x as integer = 0, byval y as integer = 0 )
   
    dim as integer srcWidth, srcHeight, srcBypp, srcPitch
    dim as integer dstWidth, dstHeight, dstBypp, dstPitch
   
    dim as integer srcXLow, srcXUpp, srcYLow, srcYUpp
    dim as integer dstXLow, dstXUpp, dstYLow, dstYUpp
   
    dim as any ptr srcPData, spr
    dim as any ptr dstPData, dpr
   
    dim as integer screenDest = 0
    dim as integer rowBytes
   
    if imageinfo( srcImg, srcWidth, srcHeight, srcBypp, srcPitch, srcPData ) then exit sub
    if dstImg <> 0 then
        if imageinfo( dstImg, dstWidth, dstHeight, dstBypp, dstPitch, dstPData ) then exit sub
    else
        dstPData = screenptr: if dstPData = 0 then exit sub
        screeninfo( dstWidth, dstHeight, , dstBypp, dstPitch )
        screenDest = 1
    end if
   
    if srcBypp <> dstBypp then exit sub
   
    srcXLow = 0: srcXUpp = srcWidth - 1
    srcYLow = 0: srcYUpp = srcHeight - 1
   
   
    '' clipping
    dstXLow = x: dstXUpp = x + srcXUpp - srcXLow
    dstYLow = y: dstYUpp = y + srcYUpp - srcYLow
   
    if dstXLow < 0 then srcXLow -= dstXLow: dstXLow = 0
    if dstYLow < 0 then srcYLow -= dstYLow: dstYLow = 0
   
    if dstXUpp >= dstWidth  then srcXUpp += dstWidth-1  - dstXUpp: dstXUpp = dstWidth-1
    if dstYUpp >= dstHeight then srcYUpp += dstHeight-1 - dstYUpp: dstYUpp = dstHeight-1
   
   
    '' find starting corner addresses
    spr = srcPData + srcYLow * srcPitch + srcXLow * srcBypp
    dpr = dstPData + dstYLow * dstPitch + dstXLow * dstBypp
   
   
    '' put image
    #macro PUT_ROWS()
    rowBytes = srcBypp * (srcXUpp - srcXLow + 1)
    for y = srcYLow to srcYUpp
        memcpy( dpr, spr, rowBytes )
        spr += srcPitch
        dpr += dstPitch
    next y
    #endmacro
   
    if screenDest then screenlock
    PUT_ROWS()
    if screenDest then screenunlock
    
end sub

sub imgput overload( _
    byval srcImg as any ptr, _
    byval dstImg as any ptr = 0, _
    byval x as integer = 0, byval y as integer = 0, _
    byval transform as IMGPUT_TRANSFORM )
   
    dim as integer srcWidth, srcHeight, srcBypp, srcPitch
    dim as integer dstWidth, dstHeight, dstBypp, dstPitch
   
    dim as integer srcXLow, srcXUpp, srcYLow, srcYUpp, srcDx, srcDy
    dim as integer dstXLow, dstXUpp, dstYLow, dstYUpp, dstDx, dstDy
   
    dim as const integer ptr srcPX1 = @srcXLow, srcPX2 = @srcXUpp
    dim as const integer ptr srcPY1 = @srcYLow, srcPY2 = @srcYUpp
   
    dim as any ptr srcPData, spr, sp
    dim as any ptr dstPData, dpr, dp
   
    dim as integer screenDest = 0
    dim as integer rowBytes
   
    if imageinfo( srcImg, srcWidth, srcHeight, srcBypp, srcPitch, srcPData ) then exit sub
    if dstImg <> 0 then
        if imageinfo( dstImg, dstWidth, dstHeight, dstBypp, dstPitch, dstPData ) then exit sub
    else
        dstPData = screenptr: if dstPData = 0 then exit sub
        screeninfo( dstWidth, dstHeight, , dstBypp, dstPitch )
        screenDest = 1
    end if
   
    if srcBypp <> dstBypp then exit sub
   
    srcDx = srcBypp: srcDy = srcPitch
    srcXLow = 0: srcXUpp = srcWidth - 1
    srcYLow = 0: srcYUpp = srcHeight - 1
   
    dstDx = dstBypp: dstDy = dstPitch
   
   
    '' set up transform
    if (transform and TRANSFORM_D1FLIP) then
        swap srcDx, srcDy
        swap srcXLow, srcYLow
        swap srcXUpp, srcYUpp
    end if
    if (transform and TRANSFORM_HFLIP) then srcDx = -srcDx: swap srcPX1, srcPX2
    if (transform and TRANSFORM_VFLIP) then srcDy = -srcDy: swap srcPY1, srcPY2
   
   
    '' clipping
    dstXLow = x: dstXUpp = x + srcXUpp - srcXLow
    dstYLow = y: dstYUpp = y + srcYUpp - srcYLow
   
    if dstXLow < 0 then srcXLow -= dstXLow: dstXLow = 0
    if dstYLow < 0 then srcYLow -= dstYLow: dstYLow = 0
   
    if dstXUpp >= dstWidth  then srcXUpp += dstWidth-1  - dstXUpp: dstXUpp = dstWidth-1
    if dstYUpp >= dstHeight then srcYUpp += dstHeight-1 - dstYUpp: dstYUpp = dstHeight-1
   
   
    '' find starting corner addresses
    spr = srcPData + *srcPY1 * abs(srcDy) + *srcPX1 * abs(srcDx)
    dpr = dstPData + dstYLow * abs(dstDy) + dstXLow * abs(dstDx)
   
   
    '' put image
    #macro PUT_TYPE( t )
        for y = srcYLow to srcYUpp
            sp = spr
            dp = dpr
            for x = srcXLow to srcXUpp
               
                *cast( t ptr, dp ) = *cast(t ptr, sp )
               
                sp += srcDx
                dp += dstDx
            next x
            spr += srcDy
            dpr += dstDy
        next y
    #endmacro
    #macro PUT_ROWS()
    rowBytes = srcBypp * (srcXUpp - srcXLow + 1)
    for y = srcYLow to srcYUpp
        memcpy( dpr, spr, rowBytes )
        spr += srcDy
        dpr += dstDy
    next y
    #endmacro
   
    if screenDest then screenlock
    if srcDx = srcBypp then
        PUT_ROWS()
    else
        select case SrcBypp
        case 1: PUT_TYPE( ubyte )
        case 2: PUT_TYPE( ushort )
        case 4: PUT_TYPE( uinteger )
        end select
    end if
    if screenDest then screenunlock
    
end sub

sub imgput overload( _
    byval srcImg as any ptr, _
    byval dstImg as any ptr = 0, _
    byval x as integer = 0, byval y as integer = 0, _
    byval transform as integer )
   
    if transform and -8 then
        select case transform mod 360
            case 0:         transform = TRANSFORM_NONE
            case 90,  -270: transform = TRANSFORM_R90
            case 180, -180: transform = TRANSFORM_R180
            case 270,  -90: transform = TRANSFORM_R270
        end select
    end if
    
    imgput( srcImg, dstImg, x, y, cast(IMGPUT_TRANSFORM, transform) )
    
end sub


screenres 320, 200, 32

dim as any ptr p = imagecreate(64, 48, rgb(64, 128, 255) )

for y as integer = 0 to 47: var y2 = (y * 256) \ 48
    for x as integer = 0 to 63: var x2 = (x * 256) \ 64
        pset p, (x, y), (x2 * y2 or y2 shl 8 or x2 shl 16)
    next x
next y

imgput( p, ,  10, 10, 0   )
imgput( p, ,  84, 10, 90  )
imgput( p, , 168, 10, 180 )
imgput( p, , 252, 10, 270 )

imgput( p, ,  10, 84, TRANSFORM_HFLIP  )
imgput( p, ,  84, 84, TRANSFORM_VFLIP  )
imgput( p, , 168, 84, TRANSFORM_D1FLIP )
imgput( p, , 252, 84, TRANSFORM_D2FLIP )

imagedestroy p

sleep
(Also, the test image is also slightly more interesting.)

Image
D.J.Peters
Posts: 8586
Joined: May 28, 2005 3:28
Contact:

Post by D.J.Peters »

realy nice work
(i dont't test it but i can compile code in or with my brain)
i won't teach you but WE can make it faster
but don't worry good job

Joshy
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Post by counting_pine »

Yeah, I'm pretty sure this code can be made faster. But if you want to do an optimised version, this code should be a pretty good point to start from.
It's written to be simple, and reasonably efficient, but I've tried to avoid doing things that would improve the speed but adversely affect the readability.
Antoni
Posts: 1393
Joined: May 27, 2005 15:40
Location: Barcelona, Spain

Post by Antoni »

The best optimisation would be to precalculate a buffer with all the rotated images, then do straight PUTS using them...
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Post by counting_pine »

That would require extra memory to hold the transformations though, and the overhead of pre-rotating them.

But there's no reason why you can't use this method as part of the process of creating such a buffer...
BasicScience
Posts: 489
Joined: Apr 18, 2008 4:09
Location: Los Angeles, CA
Contact:

Post by BasicScience »

Could this code be adapted for rotating custom text in a font image buffer (i.e. created with gfx.font.loadttf) ? The goal is to be able to use Draw String to show text from a custom font at an angle of 0, 90, 270.
counting_pine
Site Admin
Posts: 6323
Joined: Jul 05, 2005 17:32
Location: Manchester, Lancs

Post by counting_pine »

If you wanted to write a custom Draw String implementation, this could be part of it. Or, you might find it simpler to draw the text horizontally to an image buffer, then rotate it. This code doesn't feature a Trans method, although it should be easy to add one.
Post Reply