Author Topic: Help needed with one or more _MEM commands  (Read 1734 times)

0 Members and 1 Guest are viewing this topic.

Offline Stuart

  • Newbie
  • Posts: 11
    • View Profile
Help needed with one or more _MEM commands
« on: January 31, 2019, 03:19:56 am »
Basically, the program I've been working on uses a SCREEN 0 text screen that is always a minimum of 80 characters x 50 lines, and could conceiveably be as large as 80 chars x 200 lines when using QB64.  The usual size is between 66 and 130 lines depending on the screen resolution, available desktop space and _FONT used.


Here's the problem : 

I'm currently using the code below to BSAVE a copy of a certain segment of the video memory for SCREEN 0 which has already been calculated using the 'offset' and 'length' variables.

Next, the subroutine at line 1400 resets the screen size to either a 50 line screen (using a different _FONT), or...  a more than 50 line screen.  The actual number of lines will vary as described above.

After the new SCREEN 0 text screen has been established at 1400 (using the WIDTH command), the new screen will be blank, so the BLOAD statement is used to write the previously saved screen data to the beginning segment of the new SCREEN 0 video memory at &HB800 and then the KILL command deletes the temporary file that was used.

Code: QB64: [Select]
  1.            LN = CSRLIN
  2.            offset = (LN - 48) * 160: length = LN * 160
  3.            IF offset < 0 THEN offset = 0 ELSE LN = 48
  4.            '
  5.            DEF SEG = &HB800
  6.            BSAVE "tmpscrn.tmp", offset, length
  7.            GOSUB 1400
  8.            BLOAD "tmpscrn.tmp", 0
  9.            DEF SEG
  10.            KILL "tmpscrn.tmp"
  11.  

Everything works just like I want it to, but...  I don't really like having to save and then delete a file from disk each time the routine is used.

I'm pretty sure that one or more of the _MEM commands can be used to do all of this in QB64, but even after reading the info here :  https://www.qb64.org/articles/articlemem.txt I just can't figure out exactly how to go about doing it.  I'm thinking that the BSAVE and BLOAD lines just need to be replaced with the proper variant of _MEM such as _MEMIMAGE and/or _MEMCOPY or _MEMGET and _MEMPUT, but who knows?

Thanks in advance for any help that can be provided.

Offline luke

  • Administrator
  • Seasoned Forum Regular
  • Posts: 324
    • View Profile
Re: Help needed with one or more _MEM commands
« Reply #1 on: January 31, 2019, 04:21:28 am »
If you're only changing the vertical height this isn't too hard:
Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(80, 25, 0)
  2. FOR i = 1 TO 10
  3.     PRINT STRING$(i, "*")
  4.     COLOR i
  5.  
  6. character_count = _WIDTH * 10 'save 10 lines worth of characters
  7.  
  8. DIM saved AS _MEM
  9. m = _MEMIMAGE(0) '_mem reference to the display
  10. saved = _MEMNEW(m.ELEMENTSIZE * _WIDTH * 10) 'buffer to save data in
  11. _MEMCOPY m, m.OFFSET, m.ELEMENTSIZE * character_count TO saved, saved.OFFSET 'copy screen -> buffer
  12.  
  13. SCREEN _NEWIMAGE(80, 30, 0)
  14. m = _MEMIMAGE(0) 'we are now displaying a new screen, do get a new reference to it
  15. _MEMCOPY saved, saved.OFFSET, m.ELEMENTSIZE * character_count TO m, m.OFFSET 'copy buffer -> screen
  16.  
  17. _MEMFREE saved
But if you're changing the width (characters per line) or you only want to save part of a line it'll be a little more complicated because you're no longer dealing with a contiguous block of memory. At this point (or even before) consider if it isn't just easier to save the data to an array.

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Help needed with one or more _MEM commands
« Reply #2 on: January 31, 2019, 05:26:37 am »
And since it sounds like you just want to grab a segment of a screen, why don't you give these little routines a quick try:

Code: QB64: [Select]
  1. REDIM Array(0) AS STRING
  2.  
  3. FOR i = 1 TO 25 'draw an original screen
  4.     COLOR 4
  5.     LOCATE i, 1: PRINT i;
  6.     COLOR 15
  7.     LOCATE i, i: PRINT i;
  8.  
  9. ScreenSave 0, 10, 10, 20, 20, Array() 'save a portion of it
  10. CLS 'clear it
  11. ScreenRestore 0, 10, 10, Array() 'restore it exactly where it was
  12. CLS 'clear it
  13. SCREEN _NEWIMAGE(120, 50, 0) 'and make a larger screen
  14. ScreenRestore 0, 10, 10, Array() 'restore it to that larger screen
  15. ScreenRestore 0, 45, 7, Array() 'And to showcase that we can move this text elsewhere...
  16.  
  17.  
  18. SUB ScreenSave (Image, X1, Y1, X2, Y2, Array() AS STRING) 'coordinates of the screen to copy
  19.     IF X1 > X2 THEN SWAP X1, X2 'go from left to right, not right to left
  20.     IF Y1 > Y2 THEN SWAP Y1, Y2 'and from top yo bottom, not bottom to top
  21.  
  22.     DIM M AS _MEM
  23.     M = _MEMIMAGE(Image) '
  24.     E = ConvertOffset(M.ELEMENTSIZE) 'How many bytes per pixel are in memory?  Screen 0 uses 2 bytes, 256 color screens use 1, 32-bit screens use 4
  25.  
  26.     Length = (X2 - X1 + 1) * E 'Width of stored area * bytes per pixel
  27.     Rows = (Y2 - Y1 + 1) 'Number of rows to store
  28.  
  29.     REDIM Array(1 TO Rows) AS STRING 'Array to store the data
  30.     FOR i = 1 TO Rows
  31.         Array(i) = SPACE$(Length) 'Set the array to the proper length to store each row of data
  32.     NEXT
  33.  
  34.     count = 0
  35.     IF E = 2 THEN
  36.         FOR y = Y1 - 1 TO Y2 - 1 'for text screens, we need to change the coordinates from using text locations
  37.             '                     (starting at 1,1), and convert them to memory locations starting at 0,0.
  38.             count = count + 1
  39.             _MEMGET M, M.OFFSET + (y * _WIDTH + X1) * E, Array(count) 'get the rows into memory starting from the proper offset
  40.         NEXT
  41.     ELSE
  42.         FOR y = Y1 TO Y2
  43.             count = count + 1
  44.             _MEMGET M, M.OFFSET + (y * _WIDTH + X1) * E, Array(count) 'get the rows into memory starting from the proper offset
  45.         NEXT
  46.     END IF
  47.     _MEMFREE M 'Free the temp mem block
  48.  
  49. SUB ScreenRestore (Image, X1, Y1, Array() AS STRING) 'coordinates of the screen to copy
  50.     y2 = Y1 + UBOUND(Array) - 1
  51.  
  52.     DIM M AS _MEM
  53.     M = _MEMIMAGE(Image) '
  54.     E = ConvertOffset(M.ELEMENTSIZE) 'How many bytes per pixel are in memory?  Screen 0 uses 2 bytes, 256 color screens use 1, 32-bit screens use 4
  55.  
  56.     length = LEN(Array(1))
  57.     Rows = (y2 - Y1 + 1) 'Number of rows to store
  58.  
  59.     count = 0
  60.     IF E = 2 THEN
  61.         FOR y = Y1 - 1 TO y2 - 1
  62.             count = count + 1
  63.             _MEMPUT M, M.OFFSET + (y * _WIDTH + X1) * E, Array(count) 'get the rows into memory starting from the proper offset
  64.         NEXT
  65.     ELSE
  66.         FOR y = Y1 TO y2
  67.             count = count + 1
  68.             _MEMPUT M, M.OFFSET + (y * _WIDTH + X1) * E, Array(count) 'get the rows into memory starting from the proper offset
  69.         NEXT
  70.  
  71.     END IF
  72.     _MEMFREE M 'Free the temp mem block
  73.  
  74.  
  75.  
  76. FUNCTION ConvertOffset&& (value AS _OFFSET)
  77.     DIM m AS _MEM 'Define a memblock
  78.     m = _MEM(value) 'Point it to use value
  79.     $IF 64BIT THEN
  80.         'On 64 bit OSes, an OFFSET is 8 bytes in size.  We can put it directly into an Integer64
  81.         _MEMGET m, m.OFFSET, ConvertOffset&& 'Get the contents of the memblock and put the values there directly into ConvertOffset&&
  82.     $ELSE
  83.         'However, on 32 bit OSes, an OFFSET is only 4 bytes.  We need to put it into a LONG variable first
  84.         _MEMGET m, m.OFFSET, temp& 'Like this
  85.         ConvertOffset&& = temp& 'And then assign that long value to ConvertOffset&&
  86.     $END IF
  87.     _MEMFREE m 'Free the memblock
  88.  

The Screen Information is stored in a REDIM array, which you use to pass the information back and forth between the two subs, which in this case is found near the top of the code with: REDIM Array(0) AS STRING.

Note, this should work in graphic modes just the same as it does for SCREEN 0, though I haven't tested it extensively.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Stuart

  • Newbie
  • Posts: 11
    • View Profile
Re: Help needed with one or more _MEM commands
« Reply #3 on: February 07, 2019, 05:57:54 am »
If you're only changing the vertical height this isn't too hard:
Code: QB64: [Select]
  1. SCREEN _NEWIMAGE(80, 25, 0)
  2. FOR i = 1 TO 10
  3.     PRINT STRING$(i, "*")
  4.     COLOR i
  5.  
  6. character_count = _WIDTH * 10 'save 10 lines worth of characters
  7.  
  8. DIM saved AS _MEM
  9. m = _MEMIMAGE(0) '_mem reference to the display
  10. saved = _MEMNEW(m.ELEMENTSIZE * _WIDTH * 10) 'buffer to save data in
  11. _MEMCOPY m, m.OFFSET, m.ELEMENTSIZE * character_count TO saved, saved.OFFSET 'copy screen -> buffer
  12.  
  13. SCREEN _NEWIMAGE(80, 30, 0)
  14. m = _MEMIMAGE(0) 'we are now displaying a new screen, do get a new reference to it
  15. _MEMCOPY saved, saved.OFFSET, m.ELEMENTSIZE * character_count TO m, m.OFFSET 'copy buffer -> screen
  16.  
  17. _MEMFREE saved
But if you're changing the width (characters per line) or you only want to save part of a line it'll be a little more complicated because you're no longer dealing with a contiguous block of memory. At this point (or even before) consider if it isn't just easier to save the data to an array.


Thanks for your reply.  I'm sorry it took so long for me get back to you.

I did think about using an array at one time to hold the screen data which would have eliminated the unwanted disk activity when using BSAVE, BLOAD and KILL, but I was also thinking about all of the (up to 7680) PEEKs and POKEs, etc... and it seemed like it would be such a long and drawn out process that it might end up looking too flashy, so I never even bothered to try it that way.

I was able to use the code you provided to do exactly what I wanted, with a few small changes to make allowances for the _original_ 'offset' and 'length' variables that I was using for the SCREEN 0 memory segment at &HB800 (which was originally used by BSAVE and BLOAD).

Here's what I came up with :

           LN = CSRLIN
          _DELAY .001: offset = (LN - 48) * 160 '(originally used with BSAVE -- based on DEF SEG = &HB800)
           _DELAY .001: IF offset < 0 THEN offset = 0 ELSE LN = 48
           _DELAY .001: length = LN * 160
''''''''(originally used with BSAVE -- same as m.ELEMENTSIZE * _WIDTH * LN)

_DELAY .001 'Thanks Luke, for all the help with the _MEM commands below...

          _DELAY .001: DIM m AS _MEM, saved AS _MEM
           _DELAY .001: m = _MEMIMAGE(0)
''''''''This is the _MEM reference to the display.
          _DELAY .001: saved = _MEMNEW(length) 'This is the buffer to save data in.
           _MEMCOPY m, m.OFFSET + offset, length TO saved, saved.OFFSET 'Copy (up to) the first 48 of the _last 50_ lines of screen data to the buffer.
          _MEMFREE m

           GOSUB 1400
           _DELAY .001: m = _MEMIMAGE(0) '''''''''''''''''''''''We are now displaying a new screen, so get a new reference to it.
           _MEMCOPY saved, saved.OFFSET, length TO m, m.OFFSET 'Copy from the buffer to the new screen.

           _MEMFREE saved
           _MEMFREE m



Red   -- original code -- (used with the older BSAVE and BLOAD method)
Green -- new QB64 code -- (replaces the older code and doesn't cause unwanted disk activity)
Black -- always used
Blue  -- all REMarks

Note :  The "_DELAY .001" at the beginning of a few lines is used only because the program that this code is for was written in such a way that it could be loaded using either QB64 _or_ QuickBASIC 4.5. Any line that begins with an underscore will be ignored completely by QB4.5 instead of being partially loaded, causing errors, and requiring a lot of manual editing to get it working again.  Also, some of the lines are only needed for features that are available in QB64, so those lines don't get loaded by QB4.5 in order to conserve memory.

Thanks again for providing this code and the remarks; it's certainly made it a lot easier for me to understand how the _MEM commands are implemented and used together.

Stuart

Offline Stuart

  • Newbie
  • Posts: 11
    • View Profile
Re: Help needed with one or more _MEM commands
« Reply #4 on: February 07, 2019, 07:29:40 am »
And since it sounds like you just want to grab a segment of a screen, why don't you give these little routines a quick try:

Code: QB64: [Select]
  1. REDIM Array(0) AS STRING
  2.  
  3. FOR i = 1 TO 25 'draw an original screen
  4.     COLOR 4
  5.     LOCATE i, 1: PRINT i;
  6.     COLOR 15
  7.     LOCATE i, i: PRINT i;
  8.  
  9. ScreenSave 0, 10, 10, 20, 20, Array() 'save a portion of it
  10. CLS 'clear it
  11. ScreenRestore 0, 10, 10, Array() 'restore it exactly where it was
  12. CLS 'clear it
  13. SCREEN _NEWIMAGE(120, 50, 0) 'and make a larger screen
  14. ScreenRestore 0, 10, 10, Array() 'restore it to that larger screen
  15. ScreenRestore 0, 45, 7, Array() 'And to showcase that we can move this text elsewhere...
  16.  
  17.  
  18. SUB ScreenSave (Image, X1, Y1, X2, Y2, Array() AS STRING) 'coordinates of the screen to copy
  19.     IF X1 > X2 THEN SWAP X1, X2 'go from left to right, not right to left
  20.     IF Y1 > Y2 THEN SWAP Y1, Y2 'and from top yo bottom, not bottom to top
  21.  
  22.     DIM M AS _MEM
  23.     M = _MEMIMAGE(Image) '
  24.     E = ConvertOffset(M.ELEMENTSIZE) 'How many bytes per pixel are in memory?  Screen 0 uses 2 bytes, 256 color screens use 1, 32-bit screens use 4
  25.  
  26.     Length = (X2 - X1 + 1) * E 'Width of stored area * bytes per pixel
  27.     Rows = (Y2 - Y1 + 1) 'Number of rows to store
  28.  
  29.     REDIM Array(1 TO Rows) AS STRING 'Array to store the data
  30.     FOR i = 1 TO Rows
  31.         Array(i) = SPACE$(Length) 'Set the array to the proper length to store each row of data
  32.     NEXT
  33.  
  34.     count = 0
  35.     IF E = 2 THEN
  36.         FOR y = Y1 - 1 TO Y2 - 1 'for text screens, we need to change the coordinates from using text locations
  37.             '                     (starting at 1,1), and convert them to memory locations starting at 0,0.
  38.             count = count + 1
  39.             _MEMGET M, M.OFFSET + (y * _WIDTH + X1) * E, Array(count) 'get the rows into memory starting from the proper offset
  40.         NEXT
  41.     ELSE
  42.         FOR y = Y1 TO Y2
  43.             count = count + 1
  44.             _MEMGET M, M.OFFSET + (y * _WIDTH + X1) * E, Array(count) 'get the rows into memory starting from the proper offset
  45.         NEXT
  46.     END IF
  47.     _MEMFREE M 'Free the temp mem block
  48.  
  49. SUB ScreenRestore (Image, X1, Y1, Array() AS STRING) 'coordinates of the screen to copy
  50.     y2 = Y1 + UBOUND(Array) - 1
  51.  
  52.     DIM M AS _MEM
  53.     M = _MEMIMAGE(Image) '
  54.     E = ConvertOffset(M.ELEMENTSIZE) 'How many bytes per pixel are in memory?  Screen 0 uses 2 bytes, 256 color screens use 1, 32-bit screens use 4
  55.  
  56.     length = LEN(Array(1))
  57.     Rows = (y2 - Y1 + 1) 'Number of rows to store
  58.  
  59.     count = 0
  60.     IF E = 2 THEN
  61.         FOR y = Y1 - 1 TO y2 - 1
  62.             count = count + 1
  63.             _MEMPUT M, M.OFFSET + (y * _WIDTH + X1) * E, Array(count) 'get the rows into memory starting from the proper offset
  64.         NEXT
  65.     ELSE
  66.         FOR y = Y1 TO y2
  67.             count = count + 1
  68.             _MEMPUT M, M.OFFSET + (y * _WIDTH + X1) * E, Array(count) 'get the rows into memory starting from the proper offset
  69.         NEXT
  70.  
  71.     END IF
  72.     _MEMFREE M 'Free the temp mem block
  73.  
  74.  
  75.  
  76. FUNCTION ConvertOffset&& (value AS _OFFSET)
  77.     DIM m AS _MEM 'Define a memblock
  78.     m = _MEM(value) 'Point it to use value
  79.     $IF 64BIT THEN
  80.         'On 64 bit OSes, an OFFSET is 8 bytes in size.  We can put it directly into an Integer64
  81.         _MEMGET m, m.OFFSET, ConvertOffset&& 'Get the contents of the memblock and put the values there directly into ConvertOffset&&
  82.     $ELSE
  83.         'However, on 32 bit OSes, an OFFSET is only 4 bytes.  We need to put it into a LONG variable first
  84.         _MEMGET m, m.OFFSET, temp& 'Like this
  85.         ConvertOffset&& = temp& 'And then assign that long value to ConvertOffset&&
  86.     $END IF
  87.     _MEMFREE m 'Free the memblock
  88.  

The Screen Information is stored in a REDIM array, which you use to pass the information back and forth between the two subs, which in this case is found near the top of the code with: REDIM Array(0) AS STRING.

Note, this should work in graphic modes just the same as it does for SCREEN 0, though I haven't tested it extensively.

I'm sorry it took so long for me get back to you.

Thanks for the code and REMarks you provided.  It's a little more than I need at the moment because my program always uses a SCREEN 0, 80 character width display, but I'm sure I can learn a lot from it for future projects.  I like the way that you and Luke both comment each line to explain exactly what it does and why it's needed.

There were a lot of articles you had posted back on [abandoned, outdated and now likely malicious qb64 dot net website - don’t go there], one in particular that explained in great detail how the SCREEN 0 memory works -- (that 2 bytes are used for each character position, first byte = ascii value of character, and the second byte = the foreground and background colors).  I think that MOD could then be used to get those 2 values from that second, single byte.
That article was a lot of help when I had to manually calculate the 'offset' and 'length' variables needed when I was using the older BSAVE, BLOAD method to transfer screen data.  As a matter of fact, I'm still using the older 'offset' and 'length' variables _as_part_of_ the newer _MEM command(s) method.

It would be nice if those old acticles could be found and restored here at qb64.org if/when they are made available from [abandoned, outdated and now likely malicious qb64 dot net website - don’t go there] because I'm sure that lots of other people would find them interesting and useful.

Oh, and thanks again for getting the problems with VAL fixed a few years ago.  That was one of the first bugs I ever reported way back in the SDL days, and it just so happens that this program I'm working on right now is the same one I was using when I noticed the problems with VAL.

Stuart