LoginLogin
Nintendo shutting down 3DS + Wii U online services, see our post

Efficient way of drawing an image to the screen?

Root / Programming Questions / [.]

amihartCreated:
I need to draw an image to the screen for an RPG I'm working on. The issue is, I have no clue how to do this efficiently. The image I am currently trying to draw to the screen is a PPM image file I simply transferred over to my 3DS using PetitModem. I wrote some code that can do it, but there's two issues with it. (1) It requires time for the image to load, and (2) it doesn't draw it instantly. Both of these are not too big of deal on the n3DS, but on the old 3DS it draws really slowly and the loading time is pretty long. (12 seconds for one 400x240 image! As opposed to 3.5 seconds on the n3DS.) This is currently how it runs on the n3DS: It runs fast enough on the n3DS for the purpose I need it for, but nowhere near fast enough on the old 3DS. There's a 12 second loading time then the image draws onto the screen significantly slower. This is the code I'm currently using:
Spoiler
GCLS
CLS

'LOAD IMAGE 
PRINT "Loading..."
DIM OUTDATA[400*240]
FNAME$="DAT:PIC1"
GOSUB @LOADPPM

'DRAW IMAGE
CLS
GOSUB @DRAWIMAGE

'WAIT FOR USER TO PRESS A
WHILE BUTTON()!=#A:WEND
GCLS
END

'IN: FNAME$
'OUT: OUTDATA[]
@LOADPPM

  'SIZE OF ANY GIVEN PPM OF 400x240 IN BYTES
  SIZE=288015

  'LOAD IMAGE INTO INTEGER ARRAY
  'EACH INTEGER CONTAINS 4 BYTES
  DIM INDATA[SIZE/4]
  LOAD FNAME$,INDATA,FALSE  
  
  'TEMPORARY STORAGE FOR INDIVIDUAL BYTES 
  DIM TMPDATA[288015]
  
  'BREAK THE INTEGER ARRAY UP INTO ITS RESPECTIVE BYTES
  FOR I=0 TO (SIZE/4)-1
    'MOD IS NOT USED HERE ON PURPOSE!
    X0=INDATA[I]
    X1=X0
    X1=((X1/256)-FLOOR(X1/256))*256
    X2=((X0-X1)/POW(2,8))
    X2=((X2/256)-FLOOR(X2/256))*256
    X3=((X0-X1-X2*POW(2,8))/POW(2,16))
    X3=((X3/256)-FLOOR(X3/256))*256
    X4=((X0-X1-X2*POW(2,8)-X3*POW(2,16))/POW(2,24))
    X4=((X4/256)-FLOOR(X4/256))*256

    TMPDATA[I*4]=X1
    TMPDATA[I*4+1]=X2
    TMPDATA[I*4+2]=X3
    TMPDATA[I*4+3]=X4
  NEXT
  
  'CALCULATE WHEN DATA SECTION STARTS 
  NLC=0
  START=0
  WHILE NLC<3
    IF TMPDATA[START]==10 THEN NLC=NLC+1
    START=START+1
  WEND
  
  'COPY ALL RGB DATA FROM DATA SECTION INTO OUTDATA ARRAY
  FOR I=0 TO LEN(OUTDATA)-1
      R=TMPDATA[START+I*3]
      G=TMPDATA[START+I*3+1]
      B=TMPDATA[START+I*3+2]
      OUTDATA[I]=RGB(R,G,B)
  NEXT
RETURN


'IN: OUTDATA[SIZE]
'OUT: DRAWS IMAGE
@DRAWIMAGE

  X=0
  Y=0
  FOR I=0 TO LEN(OUTDATA)-1
    GCOLOR OUTDATA[I]
    GPSET X,Y
  
    X=X+1
    IF X>=400 THEN 
      X=0
      Y=Y+1
    ENDIF
  NEXT
RETURN
It works by first I have the "@LOADPPM" subroutine that loads the PPM file into an RGB array. PPM is an image format like JPEG and PNG but a lot simpler. It reads the file byte-by-byte and builds up an array containing simply the RGB data, which "@DRAWIMAGE" can then draw to the screen. As you can see, my code works, but is inefficient and I'm wondering if anyone has done something similar to this but came up with a much more efficient method.

GPSET is slow. I don't know what the solution for that is, but there is a better way to write images to the screen. As for displaying the full image quickly, use GPAGE (/and/or GCOPY). Do all the drawing hidden, then switch the visible graphic page. I wish I could explain it better but that's all I know.

GPSET is slow. I don't know what the solution for that is, but there is a better way to write images to the screen. As for displaying the full image quickly, use GPAGE (/and/or GCOPY). Do all the drawing hidden, then switch the visible graphic page. I wish I could explain it better but that's all I know.
Ah, okay. That solves the issue with it drawing the image slowly, thanks. But now I just need to figure out how to make these loading times more efficient, or else old 3DS users are going to have to deal with really long loading times. .-.

Yeah you definitely shouldn't be putting pixels on the screen individually! You'll want to look into background-based commands. Super fast. http://smilebasic.com/en/reference/#bg I think you can load a GRP to GRP pages 0 through 3 and it will just display directly, which would happen instantly. But I haven't messed with that as much, and it's not very flexible, just *bam* there's a full screen image. A more traditional way is to load your graphics in GRP format to GRP5, which by default is the page the BG commands draw from. Then you can draw the screen tile by tile to one of the background layers using BGPUT. With this you would be able to compress the amount of graphics you use if some tiles are repeated, large areas of solid color, etc. This is typically how 2D top down/sidescrollers build their maps, tile by tile, many of them repeated. And this is a very fast process even done tile by tile, even on an old 3DS. You could even get the screen all set up the way you like it, BGSAVE it to an array, and then BGLOAD that array anytime. As for getting your graphics into GRP format, you can either draw it and save it within the Smile Tool, or use transfer methods to go between PC and SB. One of the users here posted a PC GRP editor that you could use: https://smilebasicsource.com/page?pid=69

Can you explain the PPM format? Is it a plain bitmap or are the channel values stored in a sequence (first R, then G, then B)? The command you should use is GLOAD. What it does is takes an array of pixel values and writes it to the GRP layer. Of course it will still take time to unpack the PPM and turn it into image data, but it should be faster and you can write the whole image at once. Check the help for more deets and ask me anything you need to know. Hope I could help!

Yeah you definitely shouldn't be putting pixels on the screen individually! You'll want to look into background-based commands. Super fast. http://smilebasic.com/en/reference/#bg I think you can load a GRP to GRP pages 0 through 3 and it will just display directly, which would happen instantly. But I haven't messed with that as much, and it's not very flexible, just *bam* there's a full screen image. A more traditional way is to load your graphics in GRP format to GRP5, which by default is the page the BG commands draw from. Then you can draw the screen tile by tile to one of the background layers using BGPUT. With this you would be able to compress the amount of graphics you use if some tiles are repeated, large areas of solid color, etc. This is typically how 2D top down/sidescrollers build their maps, tile by tile, many of them repeated. And this is a very fast process even done tile by tile, even on an old 3DS. You could even get the screen all set up the way you like it, BGSAVE it to an array, and then BGLOAD that array anytime. As for getting your graphics into GRP format, you can either draw it and save it within the Smile Tool, or use transfer methods to go between PC and SB. One of the users here posted a PC GRP editor that you could use: https://smilebasicsource.com/page?pid=69
Ah, okay, thanks, this makes a lot of sense. I don't know why I was unpacking the entire PPM then displaying it on the screen at runtime. It makes a lot more sense to unpack it before hand and save it as something like a GRP.
Can you explain the PPM format? Is it a plain bitmap or are the channel values stored in a sequence (first R, then G, then B)? The command you should use is GLOAD. What it does is takes an array of pixel values and writes it to the GRP layer. Of course it will still take time to unpack the PPM and turn it into image data, but it should be faster and you can write the whole image at once. Check the help for more deets and ask me anything you need to know. Hope I could help!
This seems to actually be what I need, thanks! It makes a lot more sense to unpack my PPM beforehand and save an array with GSAVE and then simply load that array at runtime. If you still want to know what the PPM format is, it's basically this: The PPM format looks like this:
  • Type
  • Width Height
  • Maximum color value
  • Data Section
The first three sections are in ASCII with a newline after them. "Type" is either "P6" or "P3" (always use P6). "Width Height" would be something like "400 240". "Maximum color value" I usually use 255. The data section is binary values, each byte representing an R, G, then B value. So you have three bytes per pixel. If your image is 400x240, that means you have 96,000 pixels, meaning you need 288,000 bytes. I think I've got it figured out guys. The image is loading nearly instantly and drawing instantly. Thanks! - Other note: before I figured this out, I posted at the top that the image loaded at 12 seconds on the original 3DS and 3.5 on the n3DS. That means the n3DS was 3.4 times faster. I had an issue where an animation was based on processor speed, but I fixed it by detecting the hardware and adjusting the speed by 3.4 times. So 3.4 might be a useful figure if anyone has animations based on processor speed.

I think I've got it figured out guys. The image is loading nearly instantly and drawing instantly. Thanks!
How fast does it load now? (Another GIF ples?)

I think I've got it figured out guys. The image is loading nearly instantly and drawing instantly. Thanks!
How fast does it load now? (Another GIF ples?)
The image first loads with LOAD, which takes a fraction of the a second, and then I store it into an array to draw with GLOAD later. GLOAD loads images fast enough that you can actually draw them every frame without flickering.

I think I've got it figured out guys. The image is loading nearly instantly and drawing instantly. Thanks!
How fast does it load now? (Another GIF ples?)
The image first loads with LOAD, which takes a fraction of the a second, and then I store it into an array to draw with GLOAD later. GLOAD loads images fast enough that you can actually draw them every frame without flickering.
Cool!