Rotating a BMP image

Discussions related to graphics (2D and 3D), animation and games programming
mikeg
Posts: 101
Joined: Sat 23 Jun 2018, 19:52

Rotating a BMP image

Post by mikeg »

I was wondering if there was a ready made tool within BBC Basic that rotates a BMP image on center? Its not that I dont want to make it myself, but I wouldn't want to make it and then someone say I wasted my time. (because I know exactly how to do it using a turtle scanner encoder. (which I have made 2 programs that do both turtle and image scanning. ( The turtle IDE and BMPtoCode )

I suspect my methods of scanning and image rotation using my turtle tool may not be that fast, as BMPtoCode was effective only with small images. (and sadly, all it did was compress image information so it could be used in a program as code.)

To keep it simple, the question is:
Is there a simple way to rotate a bmp image on center?

* if there isn't, then I will work on one for the world to use.
Focus is on code subject. Feel free to judge the quality of my work.
RichardRussell

Re: Rotating a BMP image

Post by RichardRussell »

mikeg wrote: Sat 07 Mar 2020, 00:48Is there a simple way to rotate a bmp image on center?
BBC BASIC has no 'built in' statement for this, but of course it's something which is done in many existing programs so there's definitely no need to re-invent the wheel by coming up with yet another solution. 'Fast' methods that I have seen used include:
  1. Using the Windows PlgBlt API function. This transforms a rectangular image to an arbitrary parallelogram, and of course a rotated rectangle is a parallelogram! Quite easy to use, but Windows-specific so not suitable for BBC BASIC for SDL 2.0.

  2. Using the SDL2 SDL_RenderCopyEx API function, which will plot a texture with an arbitrary rotation. Again quite simple to use but specific to BBC BASIC for SDL 2.0. There are examples of its use in the supplied jigsaw.bbc and aliens.bbc programs.

  3. Using 3D graphics (via the supplied d3dlib.bbc or ogllib.bbc libraries) by texture-mapping your BMP image onto a plane rectangle and then rotating it in 3D space. Somewhat more involved, code-wise, than the above solutions but effectively cross-platform in that it's easy to make it work in both BB4W and BBCSDL. Of course you cannot easily mix 2D and 3D graphics in the same program which may be an issue in some applications.
This is by no means an exhaustive list. Another approach popular with some people is to use David Williams's GFXLIB2 library, which features sprite rotation amongst its many capabilities. It's currently BB4W-specific but I would expect it to be easily adaptable to run in BBCSDL on any x86-based platform. Could probably be ported to ARM too, but as it's written in assembly language that would need David's help.

So it's a case of choosing which is the most appropriate method for a given application. I can see an argument for writing a library that, for example, automatically uses PlgBlt in BB4W and SDL_RenderCopyEx in BBCSDL to provide a genuine cross-platform solution, but such a library does not currently exist as far as I know. I can think of a couple of people (other than me) who would have the necessary skills to write it!
RichardRussell

Re: Rotating a BMP image

Post by RichardRussell »

mikeg wrote: Sat 07 Mar 2020, 00:48Is there a simple way to rotate a bmp image on center?
Another good approach that's just occurred to me, and which is cross-platform, is to leverage the BOX2DGFX libraries available for both BBC BASIC for Windows and BBC BASIC for SDL 2.0. Although principally designed to be used with the Box2D physics engine they don't need to be, and they both contain routines for displaying a bitmap image with rotation (PROC_gfxPlot1 and PROC_gfxPlot2, which have identical effect in BBCSDL but provide a trade-off between speed and quality in BB4W).

The interface is perhaps slightly less friendly than a dedicated rotation library might provide, because the loading of the image is separated from its display (although this is of course what you want if you are plotting the same image multiple times). But of the available methods this is probably the one I would choose, having now thought of it.
RichardRussell

Re: Rotating a BMP image

Post by RichardRussell »

RichardRussell wrote: Sat 07 Mar 2020, 10:30I can see an argument for writing a library that, for example, automatically uses PlgBlt in BB4W and SDL_RenderCopyEx in BBCSDL to provide a genuine cross-platform solution
As it was simply a copy-and-paste exercise from existing programs, I've created just such a library (rather than PlgBlt I used GDIPlus in order to support images with an alpha channel, such as PNG or GIF). It is very much not written for the highest performance, for example the GDIPlus library is loaded and freed on every single call, which is wasteful but ensures that everything is cleaned up. But as a substitute for *DISPLAY, with optional rotation, it does the job. It's had very little testing, so feedback is welcome.

Code: Select all

      REM Image rotation library for BBC BASIC for Windows and BBC BASIC for SDL 2.0
      REM Version 0.00, 07-Mar-2020, (C) Richard Russell http://www.rtrussell.co.uk/

      REM Display a bitmap image with optional scaling and rotation
      REM img$ = Path and filename to image file (.bmp, .gif, .jpg, .png)
      REM x = horizontal coordinate of image *centre*, in BBC BASIC units
      REM y = vertical coordinate of image *centre*, in BBC BASIC units
      REM s = scaling (1.0 to display at original size)
      REM a = angle of rotation, counter-clockwise, in radians
      DEF PROC_displayrotate(img$, x, y, s, a)
      IF INKEY(-256) = &57 THEN
        REM 'BBC BASIC for Windows'
        LOCAL G%, H%, I%, L%, M%, P%, T%, W%, tSI{}
        DIM T% LOCAL 513

        SYS "MultiByteToWideChar", 0, 0, img$, -1, T%, 256

        SYS "LoadLibrary", "GDIPLUS.DLL" TO L%
        IF L% = 0 ERROR 100, "Couldn't load GDIPLUS.DLL"
        SYS "GetProcAddress", L%, "GdiplusStartup" TO `GdiplusStartup`
        SYS "GetProcAddress", L%, "GdipLoadImageFromFile" TO `GdipLoadImageFromFile`
        SYS "GetProcAddress", L%, "GdipDrawImageFX" TO `GdipDrawImageFX`
        SYS "GetProcAddress", L%, "GdipCreateMatrix2" TO `GdipCreateMatrix2`
        SYS "GetProcAddress", L%, "GdipRotateMatrix" TO `GdipRotateMatrix`
        SYS "GetProcAddress", L%, "GdipTranslateMatrix" TO `GdipTranslateMatrix`
        SYS "GetProcAddress", L%, "GdipDeleteMatrix" TO `GdipDeleteMatrix`
        SYS "GetProcAddress", L%, "GdipGetImageHeight" TO `GdipGetImageHeight`
        SYS "GetProcAddress", L%, "GdipGetImageWidth" TO `GdipGetImageWidth`
        SYS "GetProcAddress", L%, "GdipCreateFromHDC" TO `GdipCreateFromHDC`
        SYS "GetProcAddress", L%, "GdipSetSmoothingMode" TO `GdipSetSmoothingMode`
        SYS "GetProcAddress", L%, "GdipDeleteGraphics" TO `GdipDeleteGraphics`
        SYS "GetProcAddress", L%, "GdipDisposeImage" TO `GdipDisposeImage`
        SYS "GetProcAddress", L%, "GdiplusShutdown" TO `GdiplusShutdown`

        DIM tSI{GdiplusVersion%, DebugEventCallback%, \
        \       SuppressBackgroundThread%, SuppressExternalCodecs%}
        tSI.GdiplusVersion% = 1

        SYS `GdiplusStartup`, ^P%, tSI{}, 0
        SYS `GdipLoadImageFromFile`, T%, ^I%
        IF I% = 0 THEN
          SYS `GdiplusShutdown`, P%
          SYS "FreeLibrary", L%
          ERROR 100, "Couldn't load image """ + img$ + """"
          ENDPROC
        ENDIF

        SYS `GdipGetImageWidth`, I%, ^W%
        SYS `GdipGetImageHeight`, I%, ^H%

        SYS `GdipCreateMatrix2`, FN_f4(s), FN_f4(0), FN_f4(0), FN_f4(s), \
        \       FN_f4(@vdu.o.x%/2+x/2), FN_f4(@vdu%!212-@vdu.o.y%/2-y/2), ^M%
        SYS `GdipRotateMatrix`, M%, FN_f4(-DEGa), 0
        SYS `GdipTranslateMatrix`, M%, FN_f4(-W%/2), FN_f4(-H%/2), 0

        SYS `GdipCreateFromHDC`, @memhdc%, ^G%
        SYS `GdipSetSmoothingMode`, G%, 2
        SYS `GdipDrawImageFX`, G%, I%, 0, M%, 0, 0, 2

        SYS `GdipDeleteMatrix`, M%
        SYS `GdipDeleteGraphics`, G%
        SYS `GdipDisposeImage`, I%
        SYS `GdiplusShutdown`, P%
        SYS "FreeLibrary", L%
        SYS "InvalidateRect", @hwnd%, 0
      ELSE
        REM 'BBC BASIC for SDL 2.0'
        LOCAL s%%, t%%, W%, H%, a#, dst{}, p, ARMHF
        DIM dst{x%, y%, w%, h%}

        SYS "STBIMG_Load", img$ TO s%%
        IF @platform% AND &40 ELSE s%% = !^s%%
        IF s%%=0 ERROR 104, "Couldn't load image """ + img$ + """"
        SYS "SDL_SetHint", "SDL_RENDER_SCALE_QUALITY", "best"
        SYS "SDL_CreateTextureFromSurface", @memhdc%, s%% TO t%%
        SYS "SDL_SetHint", "SDL_RENDER_SCALE_QUALITY", "none"
        IF @platform% AND &40 ELSE t%% = !^t%%
        IF t%%=0 ERROR 105, "Unable to create texture"
        SYS "SDL_SetTextureBlendMode", t%%, 1, @memhdc%
        SYS "SDL_FreeSurface", s%%
        SYS "SDL_QueryTexture", t%%, 0, 0, ^W%, ^H%

        a# = -DEG(a)
        IF @platform% >= &2000A00 THEN
          p = PI : IF !^p = &54442D18 ARMHF = TRUE
          dst.w% = FN_f4(W% * s)
          dst.h% = FN_f4(H% * s)
          dst.x% = FN_f4((x - W% * s) / 2)
          dst.y% = FN_f4(@vdu%!212 - (y + H% * s) / 2)
          IF @platform% AND &40 THEN
            IF a#=0 THEN ?(^a#+7)=&80
            SYS "SDL_RenderCopyExF", @memhdc%, t%%, FALSE, dst{}, a#, FALSE, FALSE
          ELSE IF ARMHF THEN;
            SYS "SDL_RenderCopyExF", @memhdc%, t%%, FALSE, dst{}, a#, FALSE, FALSE
          ELSE
            SYS "SDL_RenderCopyExF", @memhdc%, t%%, FALSE, dst{}, !^a#, !(^a#+4), FALSE, FALSE
          ENDIF
        ELSE
          dst.w% = INT(W% * s + 0.5)
          dst.h% = INT(H% * s + 0.5)
          dst.x% = INT((x - W% * s) / 2 + 0.5)
          dst.y% = INT(@vdu%!212 - (y + H% * s) / 2 + 0.5)
          IF @platform% AND &40 THEN
            IF a#=0 THEN ?(^a#+7)=&80
            SYS "SDL_RenderCopyEx", @memhdc%, t%%, FALSE, dst{}, a#, FALSE, FALSE
          ELSE
            SYS "SDL_RenderCopyEx", @memhdc%, t%%, FALSE, dst{}, !^a#, !(^a#+4), FALSE, FALSE
          ENDIF
        ENDIF

        SYS "SDL_DestroyTexture", t%%, @memhdc%
      ENDIF
      ENDPROC

      DEF FN_f4(a#)LOCALp%%:p%%=^a#:IFABSa#<1E-38THEN=FALSE
      =p%%!4ANDNOT&7FFFFFFFOR(p%%!4-&38000000<<3OR!p%%>>29AND7)+(!p%%>>28AND1)
mikeg
Posts: 101
Joined: Sat 23 Jun 2018, 19:52

Re: Rotating a BMP image

Post by mikeg »

I didnt see your coded example until after I posted my response. The times are showing that my post came after yours.. I would disregard my previous post about the optional commands for doing the task. I appreciate the code you supplied . I will try it out.
Focus is on code subject. Feel free to judge the quality of my work.
mikeg
Posts: 101
Joined: Sat 23 Jun 2018, 19:52

Re: Rotating a BMP image

Post by mikeg »

I guess as per conversation in email (me , Richard and Kendall Down ):

I guess the code solution Richard supplied I can use and improve and add into my programs, as the code was posted in full as a solution in public.

Thats great!!

I will of course make a link to this subject thread so that others can easily find the publicly posted solution and test and use it!! :D

Thanks a lot Richard Russell for the post and Kendall Down for clearing up the code usage issue in email.

Of course feel free to respond quickly to this post, as I may get ambitious and get a cool demo done to post back on this forum. :o
Focus is on code subject. Feel free to judge the quality of my work.
mikeg
Posts: 101
Joined: Sat 23 Jun 2018, 19:52

Re: Rotating a BMP image

Post by mikeg »

I made a short video demo of the rotating BMP image in BBC4W and did test it in BBCSDL. ( both worked )

* The test on BBCSDL I will send a copy of the test to Richard, as review of results will likely be desired.

This test on BBC4W was really cool and here is the link to the video:

https://youtu.be/cq3QFC5rAm4
Focus is on code subject. Feel free to judge the quality of my work.
RichardRussell

Re: Rotating a BMP image

Post by RichardRussell »

mikeg wrote: Mon 09 Mar 2020, 01:36This test on BBC4W was really cool and here is the link to the video:
Performance in BBCSDL is likely to be considerably better than in BB4W, because in BBCSDL it can take advantage of hardware (GPU) acceleration. Of course if you have a poor GPU (such as some 'integrated graphics' chipsets on laptops) then the acceleration may not be very effective, but on a machine with a 'respectable' graphics card the performance in BBCSDL will probably be significantly better than in BB4W (it is here).

But I would still want to reiterate that the code I listed in the earlier post (which for some reason was deleted from the forum for several hours, but has now been restored) is not intended for 'dynamic' use (such as a video game). It is designed not for performance but for ease of use, and is effectively a direct replacement for the *DISPLAY command but with the added capability of rotation.

Just as you wouldn't normally consider using *DISPLAY for animated sprites, nor should you consider using PROC_displayrotate for that kind of application. Where 'video game' performance is required you should, as always, use David Williams's GFXLIB2 when coding in BBC BASIC for Windows and direct calls to the SDL2 API when coding in BBC BASIC for SDL 2.0. There are plenty of examples of both out there.
RichardRussell

Re: Rotating a BMP image

Post by RichardRussell »

RichardRussell wrote: Mon 09 Mar 2020, 10:02the performance in BBCSDL will probably be significantly better than in BB4W
I've just done an actual measurement, on this laptop (integrated graphics). Called 1000 times (and with a reasonably large .png image with transparency) it took 6.8 seconds in BB4W and 4.9 seconds in BBCSDL. But both are slow, in absolute terms, (about 150 fps and 200 fps respectively) which is not surprising when you consider that on every single call the file is re-read and libraries are loaded and unloaded etc. This is a terrible way to code anything if you want speed!
mikeg
Posts: 101
Joined: Sat 23 Jun 2018, 19:52

Re: Rotating a BMP image

Post by mikeg »

I did take a look at the Aliens example in BBCSDL and it was awesome. I will work with that tool.
Focus is on code subject. Feel free to judge the quality of my work.