View Full Version : Smooth in-memory image display - what's the best method?
EmbeddedMan
26-08-2015, 23:37
I'm trying to write a simple program that will load up several thousand .jpg images from disk into RAM (I have plenty of RAM on my PC) and then smoothly (i.e. double-buffered, without tearing) be able to display them one at a time in a window (not side-by-side, but like frames of a movie). Ideally I'd like to be able to 'play them back' as well as jump around to arbitrary images in the list at more than 30 fps.
I've tried doing this a canvas, but I can't find a way to load a JPG into memory and then copy that into the canvas to display.
I've tried using TImage, which can load the JPGs, but I can't get the playback smooth.
I've tried using GDI+ into a canvas, but couldn't get that to display anything at all.
Does anyone have any suggestions of a straightforward way to accomplish this?
Thanks-
*Brian
ReneMiner
27-08-2015, 09:43
Does it have to be *.jpg-format or would you be able to convert them images to something like *.bmp*- or .png-format?
If not, the the only solution i can offer is to use Freeimage-library.
Attachement contains freeimage.dll + anyTexture.tBasicU, an include-file for some basic functions that can load almost any image to create a texture for TBGL-usage and some functions example-script as this:
Uses "UI" ' needed only for Dialog_OpenFile-function
#INCLUDE "anyTexture.tBasicU"
Function TBMain()
Local scrWidth, scrHeight, scrDepth As Long '-- screen-info
Local hWin As DWord '-- handle of window
Local lBtn, hasImage As Boolean '-- left mousebutton, image available
Local sFile, sPath As String '-- filename & last path
Local lWidth, lHeight As Long '-- image size
TBGL_GetDesktopInfo(ScrWidth, ScrHeight, ScrDepth)
hWin = TBGL_CreateWindowEx( "Any texture - click to load, esc to quit", _
400, 200, scrDepth, _
%TBGL_WS_WINDOWED Or %TBGL_WS_DONTKEEPASPECTRATIO Or _
%TBGL_WS_MINIMIZEBOX Or %TBGL_WS_MAXIMIZEBOX Or _
%TBGL_WS_CLOSEBOX Or %TBGL_WS_DONTSIZE _
)
sPath = APP_ScriptPath
' -- Resets status of all keys
TBGL_ResetKeyState()
' -- open the window
TBGL_ShowWindow
While TBGL_IsWindow(hWin)
'-- esc to end
If TBGL_GetWindowKeyState(hWin, %VK_ESCAPE) Then Exit While
' left-click to load something
If TBGL_GetWindowKeyState(hWin, %VK_LBUTTON) Then
If lBtn = 0 Then
If TBGL_MouseGetPosX > -1 And TBGL_MouseGetPosY > -1 Then
hasImage = FALSE
sFile = Dialog_OpenFile(0,
"Select an image",
sPath,
"All Files (*.*)|*.*",
"*",
%OFN_FILEMUSTEXIST Or %OFN_HIDEREADONLY Or %OFN_ENABLESIZING)
' -- simply load it to slot 1
If TEXTURE_LoadData( sFile, 1) Then
TBGL_GetTextureResolution(1, lWidth, lHeight)
If lWidth > 0 And lHeight > 0 Then
TBGL_SetWindowed( hWin, lWidth, lHeight )
TBGL_RenderMatrix2D(0, lHeight, lWidth, 0) ' set the fitting matrix
hasImage = TRUE
sPath = FILE_PathSplit(sFile, %PATH_ROOTPATH)
EndIf
Else
MsgBox( 0, "Unable to open this file",, "Error" )
hasImage = FALSE
EndIf
EndIf
EndIf
lBtn = 1
Else
lBtn = 0
EndIf
If hasImage Then
' draw it
TBGL_ClearFrame
TBGL_UseTexturing TRUE
TBGL_BindTexture(1)
TBGL_Color 255, 255, 255
TBGL_BeginPoly %GL_QUADS
TBGL_TexCoord2D 0, 0
TBGL_Vertex 0, lHeight
TBGL_TexCoord2D 1, 0
TBGL_Vertex lWidth, lHeight
TBGL_TexCoord2D 1, 1
TBGL_Vertex lWidth, 0
TBGL_TexCoord2D 0, 1
TBGL_Vertex 0, 0
TBGL_EndPoly
TBGL_DrawFrame
EndIf
Wend
End Function
The amount of textures (=images) in memory at once is limited to 1024 so i don't know if that serves your needs. It's also a pool so if you use the function TEXTURE_GetHandle instead of Texture_LoadData it will make sure to load each texture just once into memory.
The code unit is able to convert & save images also to some own compressed format, recommended %TBGL_DATA_BGRA if using an alpha-channel or %TBGL_DATA_RGB so you will be able to load the image later without the need of freeImage.dll,
just look at blocks of code inside "anyTexture.tBasicU" that are enclosed by
' ### {
code
code
code
' ### }
comment those blocks of code (CTRL+B if block marked in thinAir) then you can use the unit without freeImage.dll - it still will load *.bmp, *.png, uncompressed *.tga and own formats.
If you're using thinBaisc-Version below 1.9 then you should update at least your TBGL-module, find it here:
http://www.thinbasic.com/community/showthread.php?10909-The-latest-TBGL-version
ReneMiner
27-08-2015, 11:24
However if you have thousands of images, there's a simple way to have more than 1024 images in memory.
Simply create some udt-array,
Here is an example, but it requires at least tB-version 1.9.15.0, get it from there:
http://www.thinbasic.com/community/showthread.php?12508
#MINVERSION 1.9.15.0
#INCLUDE Once "anyTexture.tBasicU"
' ###########################################################
Type tTexturedata
sData As String
sFile As String
lWidth As Long
lHeight As Long
GetFromSlot As Function
SetToSlot As Function
End Type
' ------------------------------------------------------------
Function tTexturedata.GetFromSlot(ByVal lSlot As Long) As Boolean
Me.sData = TBGL_GetTextureData(lSlot, %TBGL_DATA_BGRA)
If StrPtrLen(StrPtr(Me.sData)) Then
' ' byref passed udt-elements can cause troubles
' ' so this plain long-layover to avoid such:
Local lWidth As Long At VarPtr(Me.lWidth)
Local lHeight As Long At VarPtr(Me.lHeight)
'
' ' now can pass lWidth & lHeight byRef:
TBGL_GetTextureResolution(lSlot, lWidth, lHeight)
Me.sFile = Textures_inPool(lSlot) ' get also the filename...
Function = TRUE
Else
Me.lWidth = 0
Me.lHeight = 0
EndIf
End Function
' ------------------------------------------------------------
Function tTexturedata.SetToSlot(ByVal lSlot As Long, _
Optional ByVal setActive As Boolean = TRUE _
) As Boolean
If All( _
StrPtrLen(StrPtr(Me.sData)), _
Me.lWidth > 0 , _
Me.lHeight > 0 _
) Then
TBGL_MakeTexture Me.sData, %TBGL_DATA_BGRA, Me.lWidth, Me.lHeight, lSlot, %TEXTURE_Quality
Textures_inPool(lSlot) = Me.sFile ' informs texture-pool about the filename
If setActive Then TBGL_BindTexture(lSlot)
Function = TRUE
EndIf
End Function
' ------------------------------------------------------------
' now for example can this:
Dim myTexture(12345) As tTexturedata ' now we have space for more than 1024 images in memory...
Function TBMain()
Local scrWidth, scrHeight, scrDepth As Long '-- screen-info
Local hWin As DWord '-- handle of window
Local lBtn As Boolean '-- left mousebutton
Local Index As Long '-- use 1 and 7777 for this example
' my shipped example-images have the same size:
Local lWidth As Long = 400
Local lHeight As Long = 200
TBGL_GetDesktopInfo(ScrWidth, ScrHeight, ScrDepth)
hWin = TBGL_CreateWindowEx( "Any texture - click to change, esc to quit", _
lWidth, lHeight, scrDepth, _
%TBGL_WS_WINDOWED Or %TBGL_WS_DONTKEEPASPECTRATIO Or _
%TBGL_WS_CLOSEBOX Or %TBGL_WS_DONTSIZE _
)
' -- Resets status of all keys
TBGL_ResetKeyState()
' -- open the window
TBGL_ShowWindow
If TBGL_IsWindow(hWin) Then
' initially load 2 images to UDT-array myTexture:
' always using the same slot, which is 1
If TEXTURE_LoadData(APP_ScriptPath & "myImage2.png", 1) Then
myTexture(7777).GetFromSlot(1) ' Index is 7777 here
EndIf
If TEXTURE_LoadData(APP_ScriptPath & "myImage1.png", 1) Then
myTexture(1).GetFromSlot(1) ' Index is 1 here
EndIf
' initially we want to see myImage1, thats why it was loaded last:
Index = 1
' set the fitting matrix - same to all images here:
TBGL_RenderMatrix2D(0, lHeight, lWidth, 0)
' TBGL_BindTexture(1) can be omitted here...
' those stay as they are:
TBGL_UseTexturing TRUE
TBGL_Color 255, 255, 255
EndIf
While TBGL_IsWindow(hWin)
'-- esc to end
If TBGL_GetWindowKeyState(hWin, %VK_ESCAPE) Then Exit While
' left-click to change image
If TBGL_GetWindowKeyState(hWin, %VK_LBUTTON) Then
If lBtn = 0 Then
If TBGL_MouseGetPosX > -1 And TBGL_MouseGetPosY > -1 Then
' = clicked into the windows client-area
Select Case Index
Case 1
Index = 7777
Case Else
Index = 1
End Select
myTexture(Index).SetToSlot(1)
EndIf
EndIf
lBtn = 1
Else
lBtn = 0
EndIf
' draw it
TBGL_ClearFrame
TBGL_BeginPoly %GL_QUADS
TBGL_TexCoord2D 0, 0
TBGL_Vertex 0, lHeight
TBGL_TexCoord2D 1, 0
TBGL_Vertex lWidth, lHeight
TBGL_TexCoord2D 1, 1
TBGL_Vertex lWidth, 0
TBGL_TexCoord2D 0, 1
TBGL_Vertex 0, 0
TBGL_EndPoly
TBGL_DrawFrame
Wend
End Function
place content of the attachement into the extracted folder from post above.
Edit: oh yes, my manners, welcome to the forum Brian
Edit2: there was a type at line 50: replace comma by underscore
EmbeddedMan
27-08-2015, 14:46
Rene- thanks so much for your replies! I'll have to study those closely. I really appreciate the suggestions.
I did try to convert all of my .jpg files to .bmp files and came up with some code to load and display those. However, when using the 'convert' program (From ImagMagick), I created .bmp files that could not be read by ThinBasic. They displayed fine with every other program I tried, but there was some subtle difference in the .bmp files created by 'convert' that prevented them from working with ThinBasic. (a version difference in the bitmap header I think)
As a general thought - it seems somewhat unfortunate that using the full TBGL library to load and display .jpg files is the only way to do it smoothly in ThinBasic. Because of all of the amazing power it has, TBGL is necessarily fairly complex. Your example programs that you kindly posted are much longer than the ones I came up with when I was testing out the other methods. I'm not saying anything bad about TBGL - only that it's really too bad that ThinBasic doesn't have another way to load and display non-.bmp files. Oh well - so it goes. :-)
I'll try to digest your suggestions and see what I can come up with. Many thanks again -
*Brian
ErosOlmi
27-08-2015, 19:50
Hi Brian,
there is a thinBasic module called GDIp that wrap some GDI+ functions.
It's a module I started but never finished and fully documented.
You can have a look at 2 examples into \thinBasic\SampleScripts\GDIp\
If some GDI+ functions you need is missing I can add quite quickly and released but from sample script you can have a look at what's already in there.
The following is the list of the functions already in the module that, more or less, wrap equivalent GDI+ functions:
GDIp_LoadImageFromFile
GDIp_CreateBitMapFromFile
GDIp_CreateHBitMapFromBitmap
GDIp_DisposeImage
GDIp_DeleteGraphics
GDIp_GetImageSizeFromFile
GDIP_GetImageWidth
GDIP_GetImageHeight
GDIp_ConvertImage
GDIp_SaveImageToFile
GDIp_GetImageThumbnail
GDIp_CreateFromHDC
GDIp_DrawImage
GDIp_GetImagePixelFormat
More documentation can be found here: http://www.jose.it-berater.org/gdiplus/iframe/index.htm
Ciao
Eros
EmbeddedMan
27-08-2015, 19:54
Eros,
I'm a huge fan of ThinBasic - I really enjoy programming in it. I have tried using GDIp functions (using the examples that come with ThinBasic), but I couldn't find any way to load a graphics file and then display it in a double-buffered way (like you can with a canvas). Is there a way to load an image using a GDIp function into RAM, and then draw it to a canvas for display? (So that we get the benefit of the smooth drawing in double buffered mode) I couldn't find a way to do this.
Or maybe you have a way that we could use GDIp_DrawImage() in a double buffered mode?
*Brian
ErosOlmi
27-08-2015, 21:01
Here a code on how to use GDIp and CANVAS mix using Canvas hDC
Check DrawGraphics function in script
Place this script into \thinBasic\SampleScripts\GDIp\
It assumes it has a sub directory \images\ from which to load *.JPG images.
Click on Redraw button to rotate on images.
You need to adjust it on your needs.
Let me know.
Ciao
Eros
Uses "UI"
Uses "gdip"
Uses "File"
Randomize
' -- ID numbers of controls
Begin ControlID
%cCanvasDB
%bModifyBitmap
%bClose
End ControlID
' -- Create dialog here
Function TBMain()
Local hDlg As DWord
Dialog New 0, "Managing canvas bitmap",-1,-1, 350, 300, _
%WS_POPUP Or %WS_VISIBLE Or _
%WS_CLIPCHILDREN Or %WS_CAPTION Or _
%WS_SYSMENU Or %WS_MINIMIZEBOX, 0 To hDlg
' -- Place controls here
Dim cx, cy As Long
Dialog Pixels hDlg, 400, 400 To Units cx, cy
Control Add Canvas, hDlg, %cCanvasDB, "", 0, 0, cx, cy
Control Add Button, hDlg, %bModifyBitmap, "Redraw", 10+cx, 5 , 50, 14, Call bModifyBitmapProc
Control Add Button, hDlg, %bClose , "Close" , 10+cx, cy+5-14, 50, 14, Call bCloseProc
Dialog Show Modal hDlg, Call dlgProc
End Function
' -- Callback for dialog
CallBack Function dlgProc()
' -- Test for messages
Select Case CBMSG
Case %WM_INITDIALOG
Canvas_Attach(CBHNDL, %cCanvasDB, %FALSE)
DrawGraphics(CBHNDL)
Canvas_Redraw
Case %WM_CLOSE
' -- Put code to be executed before dialog end here
End Select
End Function
' -- Callback for close button
CallBack Function bCloseProc()
If CBMSG = %WM_COMMAND Then
If CBCTLMSG = %BN_CLICKED Then
' -- Closes the dialog
Dialog End CBHNDL
End If
End If
End Function
CallBack Function bModifyBitmapProc()
If CBMSG = %WM_COMMAND Then
If CBCTLMSG = %BN_CLICKED Then
Canvas_Attach(CBHNDL, %cCanvasDB, %TRUE)
Canvas_Clear(Rgb(0,0,0), %CANVAS_FILLSTYLE_SOLID)
DrawGraphics(CBHNDL)
End If
End If
End Function
Sub DrawGraphics(ByVal hDlg As DWord)
Static lNextImage As Long
Dim pGraphics As DWord
Dim hDC As DWord
Dim pImage As DWord
Dim nFiles As Long
Dim sFiles() As String
nFiles = DIR_ListArray(sFiles, APP_ScriptPath & "images\", "*.JPG", %FILE_NORMAL Or %FILE_ADDPATH)
If nFiles Then
hDC = Canvas_GetDC
pGraphics = GDIp_CreateFromHDC(hdc)
lNextImage += 1
If lNextImage > nFiles Then
lNextImage = 1
End If
Dialog Set Text hDlg, Format$(lNextImage) & " " & sFiles(lNextImage)
pImage = GDIp_LoadImageFromFile(sFiles(lNextImage))
GDIp_DrawImage(pGraphics, pImage, 1, 1)
'---Release GDI image
GDIp_DisposeImage(pImage)
GDIp_DeleteGraphics(pGraphics)
Canvas_Redraw
End If
End Sub
EmbeddedMan
27-08-2015, 21:53
Eros,
That looks fantastic! Would it even be possible to create an array of pImage variables, and load all of the .jpg files when the app first starts using GDIp_LoadImageFromFile() using the pImage array to hold the references to all of those images? Then, in the DrawGraphics() call, I can just call GDIp_DrawImage() using any one of the pImage values. This should allow all images to stay in RAM the whole time the program is running, and then the application will never have to hit the disk when it's swapping images, right? Then, when the app closes, I can call GDIp_DisposeImage() on all of the images in RAM.
If you think that might work, I'll re-work my code based on your example. This is exciting!
For this application, it's critical that I be able to switch between images immediately, without waiting for things to load from disk. (Loading at app startup is fine.) This is for a medical imaging application trade show demo.
Thank you so much for helping me out-
*Brian
ErosOlmi
27-08-2015, 21:58
Well, I'm not an expert in this area so I'm not able to reply yes or not with enough confidence.
The best way is to try some experimental little script able to confirm your idea.
Changing my example loading all the files in the directory in one go into an image ptr array (it's not a big job).
Than when clicking just draw the image into the DC taking image from the image ptr array
This can confirm or not your idea.
Let us know, I'm curious.
Ciao
Eros
EmbeddedMan
27-08-2015, 22:00
Sweet, I'll give it a go and let you know how it works out.
Thanks again!
*Brian
EmbeddedMan
27-08-2015, 22:26
Yup, that works perfectly. Here's my code (it just cycles through all of my frames for now):
*Brian
Uses "UI"
Uses "gdip"
Uses "File"
' -- ID numbers of controls
Begin ControlID
%cCanvasDB
%tAnimationTimer
End ControlID
Global Images(1000) As DWord
Global nFiles As Long
Global nFrame As Long
' -- Create dialog here
Function TBMain()
Local hDlg As DWord
Local i As Long
Dim sFiles() As String
' Load all images from the directory
nFiles = DIR_ListArray(sFiles, APP_ScriptPath & "frames\", "*.jpg", %FILE_NORMAL Or %FILE_ADDPATH)
If nFiles Then
For i = 1 To nFiles
Images(i) = GDIp_LoadImageFromFile(APP_SourcePath + "frames\frame (" & Trim$(Str$(i-1)) & ").jpg")
Next i
Else
Beep
Exit Function
EndIf
Dialog New Pixels, 0, "TCT Demo",-1,-1, 720, 480, _
%WS_POPUP Or %WS_VISIBLE Or _
%WS_CLIPCHILDREN Or %WS_CAPTION Or _
%WS_SYSMENU Or %WS_MINIMIZEBOX To hDlg
' -- Place controls here
Control Add Canvas, hDlg, %cCanvasDB, "", 0, 0, 720, 480
Dialog Show Modal hDlg, Call dlgProc
End Function
' -- Callback for dialog
CallBack Function dlgProc() As Long
Local i As Long
' -- Test for messages
Select Case CBMSG
Case %WM_INITDIALOG
' -- Put code to be executed after dialog creation here
Dialog Set Timer CBHNDL, %tAnimationTimer, 50, %NULL
DrawGraphics(CBHNDL, 1)
nFrame = 1
Case %WM_TIMER
DrawGraphics(CBHNDL, nFrame)
nFrame = nFrame + 1
If nFrame > 792 Then
nFrame = 1
EndIf
Case %WM_CLOSE
' -- Put code to be executed before dialog end here
For i = 1 To nFiles
GDIp_DisposeImage(Images(i))
Next i
End Select
End Function
Sub DrawGraphics(ByVal hDlg As DWord, ImageIndex As DWord)
Dim pGraphics As DWord
Dim hDC As DWord
Canvas_Attach(hDlg, %cCanvasDB, %TRUE)
' Canvas_Clear(Rgb(0,0,0), %CANVAS_FILLSTYLE_SOLID)
hDC = Canvas_GetDC
pGraphics = GDIp_CreateFromHDC(hdc)
GDIp_DrawImage(pGraphics, Images(ImageIndex), 1, 1)
Canvas_Redraw
GDIp_DeleteGraphics(pGraphics)
End Sub
EmbeddedMan
17-09-2015, 21:02
Eros,
As an update, the method you describe in your last example of this thread works *great*. The frames of my application are extremely smooth, and I can control them extremely accurately. It looks really really sharp.
Thanks again for your suggestions and help - another win for TB!
*Brian
ErosOlmi
18-09-2015, 12:20
Great!
:occasion:
You know ... we are very curious :yes:
Maybe one day you will show us some screen shots of your creations (if not something reserved).
Ciao
Eros
EmbeddedMan
22-09-2015, 16:44
Eros,
Well, in some sense, screen shots of this application are not very interesting, as they just consist of whatever JPEG frame is being displayed at the moment. In other words, the app simply displays the frames from a directory of JPG images - there are no controls in the window. So I'm not going to put up a screenshot. However, I can describe the application and I've put the full source code (to both the embedded piece and the PC side) below so you can see what I've done.
Basic Application Idea:
I needed an application that would load up a directory full of JPG images, and play them back (i.e. display a particular one) based on the input from a small embedded board (a chipKIT DP32, which is a PIC32 based board programmed with the Microchip PIC32 version of Arduino, connected via USB). As a user turns a knob on the embedded board, the application needs to quickly and seamlessly display the proper image based on the knob position. The images are captured from a medical imaging device, and this whole thing is basically a way for us to show off a 'simulation' of the main medical device we make at trade shows, without bringing along a lot of messy equipment to actually do live imaging.
There is a button on the DP32 board, which, when pressed, displays frame 0, which is a 'special' JPG image. (it shows an overlay of the body part being imaged). We also need to give the impression to the user that the system is 'live', so I dither back and forth a couple of frames no matter where you have the knob. This gives the impression that things are moving.
The application looks for a directory called 'frames', and reads in all of the JPG files from that directory. The really sweet thing is that due to the method used to read in the frames and the canvas object used to display them, there is zero flicker, and the response is instant. It's fantastic. It also uses surprisingly little RAM.
I'm actually running this whole application on a $60 USD Windows 8 tablet (with HDMI output so we can display it on a big screen) and the tablet can keep up with the knob position no problem. It has plenty of horsepower to make everything nice and smooth. I currently only have about 700 frames, but we'll be taking more images soon and that will likely at least triple. The images I'm using are at the screen resolution of the tablet (800 x 1280).
I've only spent a limited amount of time on this, but ThinBasic allowed me to pull it off beautifully. Since this first prototype went so smooth (and was far under-budget) there are now plans to turn this application into a full training simulator that we could use to train people on how to use our device. Since the computer is aware of how fast the user is turning the knob, we can easily throw up warnings if they're moving too fast or too slow, or treating the wrong region, etc. There are lots of possibilities.
So here's the sketch I'm running on the chipKIT DP32 board:
#define BTN3 1
void setup() {
Serial.begin(9600);
pinMode(A3, INPUT);
digitalWrite(A3, LOW);
pinMode(BTN3, INPUT);
digitalWrite(BTN3, LOW);
}
void loop() {
int sensorValue = analogRead(A3);
if (digitalRead(BTN3) == HIGH)
{
sensorValue = sensorValue + 1024;
}
Serial.println(sensorValue, DEC);
delay(10); // Run this at about 100 Hz
}
And here's the full ThinBasic application:
' Application written to demo mulitple JPEG images smoothly animated
' using a hardware input device to determine which frame to display.
'
' A small chipKIT development board (DP32) is connected to an analog
' potentiomenter, and sends analog readings from the pot (from 0 to 1023)
' up to the PC every 10ms.
' If BTN3 is pressed on the DP32, 1024 is added to the ADC value before
' sending it up to the PC.
' A simple sketch is running on the DP32 to provide this functionality.
'
' Written by Brian Schmalz, with lots of help and ideas from the
' fantastic ThinBasic forum.
Uses "UI"
Uses "COMM"
Uses "GDIP"
Uses "FILE"
Begin ControlID
%cCanvas
%tAnimationTimer
End ControlID
%TIMER_DELAY = 10 ' Timer delay (in milliseconds, not very accurate below about 100)
%MAXIMUM_ADC_VALUE = 1023 ' From DP32, maximum value from pot position
%MAX_ORBIT_FACTOR = 1 ' Number of frames to add to and substract from CurrentFrame to show orbit
$FRAME_DIRECTORY = "Frames" ' Name of directory where all of our frames are contained
Global hImg(1) As Long ' Array of images (frames) to display
Global CurrentFrame As Long ' Index into hImg() of which frame to display right now
Global LastFrame As Long ' The last frame number we displayed
Global hComm As Long ' COM port handle that DP32 is talking to us on
Global nFileCount As Long
Function TBMain()
Local hDlg As DWord
Local i As Long
Local nWidth, nHeight As Long
Local sFramesFileNames() As String
Local sComPort As String
' Grab the proper com port number for this PC from the file
sComPort = FILE_Load(APP_SourcePath & "comport.txt")
If (Len(sComPort) = 0) Then
Beep
Exit Function
EndIf
' Read in all *.jpg file names from Frames directory
nFileCount = DIR_ListArray(sFramesFileNames, APP_SourcePath & $FRAME_DIRECTORY & "\", "*.jpg", %FILE_NORMAL Or %FILE_ADDPATH)
If (nFileCount = 0) Then
Beep
Exit Function
EndIf
' Sort the array of file names so they're in the right order
Array Sort sFramesFileNames(), AsFiles
' Make sure hImg array is the right size for the number of frames we have
ReDim hImg(nFileCount)
' Load each jpg file into a GDIp grapihcs image
For i = 1 To nFileCount
hImg(i) = GDIp_LoadImageFromFile(sFramesFileNames(i))
If hImg(i) = %NULL Then
Beep
Exit Function
End If
Next i
' Look up dimensions of the first frame, and make our window that size
GDIp_GetImageSizeFromFile(sFramesFileNames(1), nWidth, nHeight)
' Open up the com port
hComm = COMM_FreeFile
' Open the com port listed in the comport.txt file. Would be cool to look up in registry where
' DP32 board is (com port) and use that instead.
COMM_Open("\\.\COM" & sComPort, hComm)
If Err <> 0 Then
Beep
Exit Function
End If
' This appears to be necessary to 'start things going' on the DP32
COMM_Send(hComm, " ")
' Because our tablet is 800 x 1024, we use those values for this window
Dialog New Pixels, 0, "Demo App", -1, -1, 800, 1280, _
%WS_POPUP Or %WS_VISIBLE Or %WS_CAPTION Or %WS_SYSMENU Or %WS_MINIMIZEBOX To hDlg
Control Add Canvas, hDlg, %cCanvas, "", 0, 0, nWidth, nHeight
' This is the big deal - the reason we like to use a canvas. Because of the "TRUE" parameter, which
' stands for "wait until a Canvas_Redraw" call to update the canvas, and it make very very smooth updates.
Canvas_Attach(hDlg, %cCanvas, TRUE)
Canvas_Scale Pixels
Canvas_Redraw
Dialog Show Modal hDlg, Call dlgCallback
End Function
callback function dlgCallback() as long
Local i As Long ' index variable
Local pGraphics As DWord ' Points to our graphics thing
Local hDC As DWord ' Handle to some graphics thing
Local ADCValue As Long ' Numerical value that the ADC on the DP32 board sends us (position of pot)
Static sAccumulator As String ' String we build up from DP32, then convert into ADCValue
Local nBytes As Long ' Number of bytes DP32 just sent us
Local sBuffer As String ' String of bytes DP32 just sent us
Static OrbitFactor As Long ' Cylical offset in frame count to give impression of orbiting
' -- Test for messages
Select Case CBMSG
Case %WM_INITDIALOG
' -- Put code to be executed after dialog creation here
Dialog Set Timer CBHNDL, %tAnimationTimer, %TIMER_DELAY, %NULL
CurrentFrame = 1
LastFrame = 1
sAccumulator = ""
Case %WM_TIMER
' Pull in all buytes from the DP32
nBytes = COMM_Get(hComm, %COMM_RXQUE)
If nBytes > 0 then
COMM_Recv(hComm, nBytes, sBuffer)
For i = 1 To nBytes
' If we have a CR, then parse the number
If Mid$(sBuffer, i, 1) = Chr$(13) And Len(sAccumulator) > 0 Then
' Convert from string that DP32 sent us to number
ADCValue = Val(sAccumulator)
' Always clear out our buffer once we've found a value
sAccumulator = ""
' Pick off the state of the BTN3 from the DP32
If (ADCValue > %MAXIMUM_ADC_VALUE) Then
CurrentFrame = 1
ADCValue = ADCValue - 1023
Else
' Limit scaled ADC value to 1 to %MAXIMUM_ADC_VALUE
If ADCValue > %MAXIMUM_ADC_VALUE Then
ADCValue = %MAXIMUM_ADC_VALUE
End If
If ADCValue < 1 Then
ADCValue = 1
End If
' Scale CurrentFrame to ADCFilteredValue
CurrentFrame = (ADCValue * nFileCount)/%MAXIMUM_ADC_VALUE
' Limit CurrentFrame to 1 to %NUMBER_OF_FRAMES taking into account MAX_ORBIT_FACTOR
If CurrentFrame > (nFileCount - %MAX_ORBIT_FACTOR) Then
CurrentFrame = (nFileCount - %MAX_ORBIT_FACTOR)
End If
If CurrentFrame < 2 + %MAX_ORBIT_FACTOR Then
CurrentFrame = 2 + %MAX_ORBIT_FACTOR
End If
OrbitFactor = OrbitFactor + 1
If (OrbitFactor > %MAX_ORBIT_FACTOR) Then
OrbitFactor = -%MAX_ORBIT_FACTOR
EndIf
' Add in 'orbit factor'
CurrentFrame = CurrentFrame + OrbitFactor
EndIf
Else
sAccumulator = sAccumulator + Mid$(sBuffer, i, 1)
End If
Next i
' Check to see if the displayed frame needs to change
If CurrentFrame <> LastFrame Then
LastFrame = CurrentFrame
hDC = Canvas_GetDC
pGraphics = GDIp_CreateFromHDC(hdc)
GDIp_DrawImage(pGraphics, hImg(CurrentFrame), 1, 1)
'---Release GDI image
GDIp_DeleteGraphics(pGraphics)
' Allow the update to happen
Canvas_Redraw
End If
End If
Case %WM_CLOSE
COMM_Close(hComm)
End Select
End Function
Oh, and this app is working perfectly as a bundled application that starts up right when the tablet boots. My next task is to figure out how to put the tablet in a kiosk-type mode, where the window boarder doesn't show up at all, and it's just the JPG images that take up the whole screen.
Thanks so much for this fantastic language-
*Brian
ErosOlmi
24-09-2015, 06:55
Hi Brian,
thanks a lot for sharing all those interesting details and the script source code. It can greatly help others having similar needs.
Very interesting problem and clever solutions. I really like when software is used to help real life situations.
Wish the best for the future of your system.
Great
Eros
Petr Schreiber
24-09-2015, 09:04
Brian,
thanks for sharing, it always make my day to see thinBasic is used to help someone.
I had a look at your code, I found few places you can optimize - if you want!
a)
Retrieving OS desktop resolution can be achieved via:
DESKTOP GET SIZE TO width, height
...so you can retrieve it dynamically to allow porting your app easily once tablets with different resolution come.
b)
Cycling variables can be done easily in ThinBASIC. Compare the original:
OrbitFactor = OrbitFactor + 1
If (OrbitFactor > %MAX_ORBIT_FACTOR) Then
OrbitFactor = -%MAX_ORBIT_FACTOR
EndIf
...to simple:
orbitFactor = CYCLE_Next(orbitFactor, -%MAX_ORBIT_FACTOR, %MAX_ORBIT_FACTOR, 1)
c)
Normalizing value to specific bounds, like you do it here:
If ADCValue > %MAXIMUM_ADC_VALUE Then
ADCValue = %MAXIMUM_ADC_VALUE
End If
If ADCValue < 1 Then
ADCValue = 1
End If
...can be achieved this way:
ADCValue = MinMax(ADCValue, 1, %MAXIMUM_ADC_VALUE)
...and the same for CurrentFrame.
d)
Shortcut for doing this:
CurrentFrame = CurrentFrame + OrbitFactor
...is this one:
CurrentFrame += OrbitFactor
e)
Carriage Return is built-in, so instead of:
Chr$(13)
...you can use directly:
$CR
Petr
ErosOlmi
24-09-2015, 09:11
Wow Petr
you just recalled from my oooold neurons memory of that days when we developed all those nice functions
:yes:
Petr Schreiber
24-09-2015, 09:17
... and sure more will come! :)
Petr
EmbeddedMan
28-09-2015, 17:33
Petr,
Thanks! Those are all great tips, I'll put them in my code right away.
Do you know if there's an easy way to scale an image once I've loaded it into memory? It would be best if I took the screen resolution dynamically at run time (as you suggest) and then scaled the images as I'm displaying them. Is there a way with GDIp?
*Brian
ErosOlmi
28-09-2015, 21:53
A quick way is to delete/recreate the Canvas control at every Window re-size the same size as the window client area.
Than re-draw image into the newly created canvas having the same client area size of the window.
See below example in which I set the window re-sizable and at every re-size (%WM_SIZE message) the canvas control is killed and recreated.
Than image draw takes place in a DC of the same size of the window client area.
The rest is done by GDI+
Execute the script and try to re-size the window and see.
(you need to have an \image\ sub directory in which to have some JPG image to be loaded)
Uses "UI"Uses "gdip"
Uses "File"
' -- ID numbers of controls
Begin ControlID
%cCanvasDB
%tAnimationTimer
End ControlID
Global Images() As DWord
Global nFiles As Long
Global nFrame As Long
'----------------------------------------------------------
'---Dialog Callback for the main window
'----------------------------------------------------------
Function TBMain()
Local hDlg As DWord
Local i As Long
Dim sFiles() As String
' Load all images from the directory
' ---CHANGE AS NEEDED---
nFiles = DIR_ListArray(sFiles, APP_ScriptPath & "images\", "*.jpg", %FILE_NORMAL Or %FILE_ADDPATH)
If nFiles Then
ReDim Images(nFiles)
For i = 1 To nFiles
Images(i) = GDIp_LoadImageFromFile(sFiles(i))
Next i
Else
Beep
Exit Function
EndIf
' Dialog New Pixels, 0, "GDI+ and Canvas Demo",-1,-1, 720, 480, _
' %WS_POPUP Or %WS_VISIBLE Or _
' %WS_CLIPCHILDREN Or %WS_CAPTION Or _
' %WS_SYSMENU Or %WS_MINIMIZEBOX To hDlg
Dialog New Pixels, 0, "thinBasic using GDI+", -1, -1, 720, 480, _
%WS_OVERLAPPEDWINDOW Or %WS_CLIPCHILDREN, _
0 To hDlg
Dialog Show Modal hDlg, Call dlgProc
End Function
'----------------------------------------------------------
'---Dialog Callback for the main window
'----------------------------------------------------------
CallBack Function dlgProc() As Long
Static idxImage As long
Local cx, cy As Long
'---
Select Case CBMSG
Case %WM_INITDIALOG
'Dialog Get Client CBHNDL To cx, cy
''---Place controls here
'Control Add Canvas, CBHNDL, %cCanvasDB, "", 0, 0, cx, cy
'---
Dialog Set Timer CBHNDL, %tAnimationTimer, 10, %NULL
idxImage = 1
Image_Draw(CBHNDL, idxImage)
Case %WM_SIZE
'---At every window resize ... delete canvas control and recreate it the same size as the window client area
Dialog Get Client CBHNDL To cx, cy
'---Place controls here
Control Kill CBHNDL, %cCanvasDB
Control Add Canvas, CBHNDL, %cCanvasDB, "", 0, 0, cx, cy
Case %WM_TIMER
Image_Draw(CBHNDL, idxImage)
idxImage += 1
If idxImage > UBound(Images) Then
idxImage = 1
EndIf
Case %WM_CLOSE
Images_Dispose
End Select
End Function
'----------------------------------------------------------
'---
'----------------------------------------------------------
Function Images_Load() As Long
End Function
'----------------------------------------------------------
'---
'----------------------------------------------------------
Function Images_Dispose() As Long
Local lIdx As Long
'---Clear allocated memory for images
For lIdx = 1 To UBound(Images)
GDIp_DisposeImage(Images(lIdx))
Next i
End Function
'----------------------------------------------------------
'---
'----------------------------------------------------------
Function Image_Draw(ByVal hDlg As DWord, ByVal ImageIndex As DWord)
Dim pGraphics As DWord
Dim pThumbnail As DWord
Dim hDC As DWord
Local cx, cy As Long
If ImageIndex < 1 Or ImageIndex > UBound(Images) Then
Exit Function
End If
Canvas_Attach(hDlg, %cCanvasDB, %TRUE)
Canvas_Clear(Rgb(0,0,0), %CANVAS_FILLSTYLE_SOLID)
hDC = Canvas_GetDC
pGraphics = GDIp_CreateFromHDC(hdc)
Dialog Get Client hDlg To cx, cy
'---Create a thumbnail the same size of the dialog client size
pThumbnail = GDIp_GetImageThumbnail(Images(ImageIndex), cx, cy)
'GDIp_DrawImage(pGraphics, Images(ImageIndex), 1, 1)
GDIp_DrawImage(pGraphics, pThumbnail, 1, 1)
Canvas_Redraw
GDIp_DisposeImage(pThumbnail)
GDIp_DeleteGraphics(pGraphics)
End Function
EmbeddedMan
29-09-2015, 17:18
Ooooo! That's fantastic. I merged your technique into my code, and it works absolutely wonderfully! Thanks so much.
*Brian