Text Art and Animation

Creating Games? Developing something for fun?

Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, robinmiller

Post Reply
Hutchboy
Posts: 84
Joined: Wed Aug 01, 2018 2:57 pm
Contact:

Text Art and Animation

Post by Hutchboy » Sun Oct 13, 2024 3:32 am

Hi,

Attached is a stack that converts images to text field representations of the image as well as a second card that implements some simple animation.

I've included the ability to save these text images to file and load them. Note that the color information is encoded in the file format so if you open it in a text editor you won't see the image.

A couple of potential use cases:
1. Digital Matrix Simulator: In many pinball games a 128 x 32 digital matrix display (dmd) had been used for displaying scores and short animations. (Look up "circus pinball dmd" for a neat example).
2. Text Adventures

Note for card 1:
1. You can enable the vertical scrollbar to see that it works as expected, I just left it off as the default.
2. I think the best spacing setting is when the textHeight is less than the textSize by 2. Experiment with the sliders to see.

Happy Coding,
Mike
[Attachment deleted, superseded below]
Last edited by Hutchboy on Wed Oct 16, 2024 10:59 pm, edited 1 time in total.

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4129
Joined: Sun Jan 07, 2007 9:12 pm

Re: Text Art and Animation

Post by bn » Sun Oct 13, 2024 3:32 pm

Hi Mike,

That is yet another very clever stack you made. Thank you.

Regarding textSize slider: changing the textSize automatically adjusts also the textHeight.
The following code keeps the textHeight to what it was set to via the scrollbar for density:

Code for scrollbar for textSize:

Code: Select all

on scrollbarDrag pNewPosition
   local tTH
   lock screen
   put the textheight of field "TextArtField" into tTH
   set the textSize of field "TextArtField" to round(pNewPosition)
   set the textHeight of field "TextArtField" to tTH
end scrollbarDrag
I was intrigued by the relatively long time it takes to convertImageToText.
It is for me just short of 4 seconds for the "made with livecode" and "indiana jones" images.

I thought this might be something for "StyledText".

I rewrote handler "ImageToAscii" in the card script to use styledText instead of operating on the chars of the field directly.
StyledText is not the most obvious way to build complex text as in your case.
However now I get build times around 120 milliseconds for those two images.
I am not suggesting to use styledText, it is just a showcase for using it.
(You could also use htmlText instead of styledText to get a similar speed increase)

Code: Select all

command ImageToAscii
   local tImageWidth, tImageHeight
   local tDisplayChar
   local tStartTime, tEndTime, tElapsedTime
   local tImageData, tRed, tGreen, tBlue
   local x, y
   local tLineSize, tBytesPerPixel
   
   lock screen
   
   -- Check if the selected image ID is valid
   if gSelectedImageID is empty or not (there is an image ID gSelectedImageID) then
      answer "No image selected. Please double-click an image to select."
      exit ImageToAscii
   end if
   
   -- Get image dimensions
   
   put the width of image ID gSelectedImageID into tImageWidth
   put the height of image ID gSelectedImageID into tImageHeight
   
   -- Check if the image has valid dimensions 
   if not ((tImageWidth <= kImageMaxWidth and tImageHeight <= kImageMaxHeight)) then
      answer "The maximum image size is 130 x 100 pixels"
      exit ImageToAscii
   end if
   
   -- Determine the character to use based on the selected radio button
   
   if the hilite of button "BlockCharButton" of group "CharSelectionGroup" then
      put kBlockChar into tDisplayChar
   else if the hilite of button "DotCharButton" of group "CharSelectionGroup" then
      put kDotChar into tDisplayChar
   end if
   
   -- Start timing
   put the milliseconds into tStartTime
   
   -- Initialize the output field
   put empty into field "TextArtField"
   
   -- Lock screen updates for performance
   lock screen; lock messages
   
   -- Get the image data
   put the imageData of image ID gSelectedImageID into tImageData
   
   -- Each pixel is represented by 4 bytes in imageData
   put 4 into tBytesPerPixel
   
   -- Calculate line size
   put tImageWidth * tBytesPerPixel into tLineSize
   
   local tBytePosition, tLineNumber, tRunsNum, tStyleA, tCurrRGB, tPrevRGB, tPathToStyle
   
   
   put "style,textColor" into tPathToStyle
   split tPathToStyle by comma
   
   --   Loop through each pixel
   repeat with y = 0 to tImageHeight - 1
      put y + 1 into tLineNumber
      put 0 into tRunsNum
      put empty into tPrevRGB
      repeat with x = 0 to tImageWidth - 1
         -- Calculate the byte position in the imageData
         
         put (y * tLineSize) + (x * tBytesPerPixel) + 1 into tBytePosition
         
         -- Extract RGB values
         -- Byte 1: Ignored (tBytePosition)
         -- Byte 2: Red (tBytePosition + 1)
         -- Byte 3: Green (tBytePosition + 2)
         -- Byte 4: Blue (tBytePosition + 3)
         put charToNum(char tBytePosition + 1 of tImageData) into tRed
         put charToNum(char tBytePosition + 2 of tImageData) into tGreen
         put charToNum(char tBytePosition + 3 of tImageData) into tBlue
         
         put tRed & comma & tGreen & comma & tBlue into tCurrRGB
         
         if tCurrRGB <> tPrevRGB then
            add 1 to tRunsNum
            put tCurrRGB into tStyleA[tLineNumber]["runs"][tRunsNum][tPathToStyle]
            put tDisplayChar after tStyleA[tLineNumber]["runs"][tRunsNum]["Text"]
            put tCurrRGB into tPrevRGB
         else
            put tDisplayChar after tStyleA[tLineNumber]["runs"][tRunsNum]["Text"]
         end if
         
         --         -- Set the text color and add character to the output field
         --         put tDisplayChar after field "TextArtField"
         --         set the textColor of  codeUnit -1 of field "TextArtField" to (tRed & "," & tGreen & "," & tBlue)
         
      end repeat
      --put return after field "TextArtField"
   end repeat
   
   set the styledText of field "TextArtField" to tStyleA
   
   -- End timing
   put the milliseconds into tEndTime
   put (tEndTime - tStartTime) / 1000 into tElapsedTime
   
   -- Display the elapsed time in the TimerField
   put "conversion time: " & tElapsedTime & " secs" into field "TimerField"
   
   -- Unlock screen updates
   unlock screen; unlock messages
end ImageToAscii
Kind regards
Bernd

dunbarx
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 10055
Joined: Wed May 06, 2009 2:28 pm

Re: Text Art and Animation

Post by dunbarx » Sun Oct 13, 2024 4:10 pm

Bernd.

You are one who, like myself, would always rather play with LC than pay attention to your day job. :wink:

Craig

Hutchboy
Posts: 84
Joined: Wed Aug 01, 2018 2:57 pm
Contact:

Re: Text Art and Animation

Post by Hutchboy » Sun Oct 13, 2024 4:42 pm

Bernd,

Thanks for the script optimizations. I hadn't thought to explore styledText or HTML. I knew the conversion time was slower than I wanted, so this will help.

I've already updated card two for animations to add the choice of character and to adjust the textSize and textHeight with sliders, so your changes should really help the speed there since it is not very smooth even at max 100 setting (=10 ms).

I have created a binary file format, .dat, with an included file header to be able to save and load text animation sequences from external locations using standard dialogues. As soon as I incorporate your changes, check out styledText/HTML, and create some sample animations and a use case demo or two I'll post an update.

BTW, I started out on this path using a mono-spaced font and seeing which characters have high to low density, then use those for the image conversion. I think this is the more traditional way of creating ascii art and it works ok, but then the little light went on and I changed to this path. I might revisit it for what I would imagine would be much faster animation.

Thanks,

Mike

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4129
Joined: Sun Jan 07, 2007 9:12 pm

Re: Text Art and Animation

Post by bn » Mon Oct 14, 2024 12:44 am

Hi Mike,

This is the modification of your import handler "LoadAsciiFromFile" of the card script of card 1 for the files one can export.
It again uses styledText.

Code: Select all

command LoadAsciiFromFile pTargetField
   -- Declare local variables
   local tData, tLines, tLine, tChar, tTextColor, tCharIndex, tFilePath
   local tEncodedData, tTotalLines
   
   -- Ask the user to select a file to load
   answer file "Open Text Art File:" --without type
   if it is empty then exit LoadAsciiFromFile
   put it into tFilePath
   
   -- Read data from file in binary mode
   open file tFilePath for binary read
   read from file tFilePath until EOF
   put it into tEncodedData
   close file tFilePath
   
   -- Decode data from UTF-8
   put textDecode(tEncodedData, "UTF-8") into tData
   
   -- Split data into lines
   set the lineDelimiter to return
   put the number of lines in tData into tTotalLines
   
   -- Initialize the target field
   put empty into field pTargetField
   
   -- Process each line to reconstruct the colored text
   lock screen
   --   repeat with tCharIndex = 1 to tTotalLines
   --      put line tCharIndex of tData into tLine
   --      if tLine is empty then
   --         put return after field pTargetField
   --         next repeat
   --      end if
   --      set the itemDelimiter to "|"
   --      put item 1 of tLine into tChar
   --      put item 2 of tLine into tTextColor
   --      if tTextColor is empty then put "0,0,0" into tTextColor -- Default to black if no color
   --      put tChar after field pTargetField
   --      set the textColor of char -1 of field pTargetField to tTextColor
   --   end repeat
  
   local tBytePosition, tLineNumber, tRunsNum, tStyleA, tCurrRGB, tPrevRGB, tPathToStyle
   local tDisplayChar
   
   put "style,textColor" into tPathToStyle
   split tPathToStyle by comma
   
   set the itemDelimiter to "|"
   put item 1 of line 1 of tData into tDisplayChar
   
   put 1 into tLineNumber
   put 0 into tRunsNum
   put empty into tPrevRGB
   repeat for each line aLine in tData
      if aLine is empty then 
         add 1 to tLineNumber
         put 0 into tRunsNum
         put empty into tPrevRGB
         next repeat
      end if
      
      put item 2 of aLine into tCurrRGB
      
      if tCurrRGB <> tPrevRGB then
         add 1 to tRunsNum
         put tCurrRGB into tStyleA[tLineNumber]["runs"][tRunsNum][tPathToStyle]
         put tDisplayChar after tStyleA[tLineNumber]["runs"][tRunsNum]["Text"]
         put tCurrRGB into tPrevRGB
      else
         put tDisplayChar after tStyleA[tLineNumber]["runs"][tRunsNum]["Text"]
      end if
   end repeat
   set the styledText of field pTargetField to tStyleA
   
   unlock screen
end LoadAsciiFromFile

Kind regards
Bernd

Hutchboy
Posts: 84
Joined: Wed Aug 01, 2018 2:57 pm
Contact:

Re: Text Art and Animation

Post by Hutchboy » Wed Oct 16, 2024 11:05 pm

Hi,

With much thanks to Bernd on code to improve rendering speeds I have made a number of improvements for version 1.1. I did have to scale my sample images down from 130 x 100 pixels to be able to post it here. I added the ability to save animations in an uncompressed format (.dat) and a compressed version (.datc). This compression made 12 130 x 100 pixel images in an animation go from 2 mb to ~550 kb. I also added a help screen to explain things.

Mike
Attachments
Animation Set 1 small.zip
(128.07 KiB) Downloaded 240 times
Text Art and Field Animation version 1.1.livecode.zip
(109.59 KiB) Downloaded 328 times

bn
VIP Livecode Opensource Backer
VIP Livecode Opensource Backer
Posts: 4129
Joined: Sun Jan 07, 2007 9:12 pm

Re: Text Art and Animation

Post by bn » Thu Oct 17, 2024 8:12 pm

Hi Mike,

That is a very nice update. With an excellent speed increase.

I hope you do not mind if I suggest some possible variants.

You now use styledText as a way to load fields. I would have thought to also use styledText to save the "images" to a file.
If you make an array of the styledText arrays and arrayEncode + compress that array you have an increase in file size but you can streamline your loading and display of the animation.
As a proof of principle I made two button:

Button 1 to save styledText from the loaded image for animation:

Code: Select all

on mouseUp
   local tFrames, tOneFrame, tFrameName, tFrameShort, tFrameList
   local tAllStyledTextA, tCounter, tImgData, tLson, tLength, tStackPath, tResult
   
   put "Frame" into tFrameShort
   repeat with i = 1 to 12
      put tFrameShort & i & cr after tFrameList
   end repeat
   delete char -1 of tFrameList
   
   repeat for each line aFrame in tFrameList
      add 1 to tCounter
      put imageToAsciiData(aFrame) into tAllStyledTextA[tCounter]
   end repeat
   
   put arrayEncode(tAllStyledTextA) into tLson
   put compress (tLSon) into tLson
   
   put the length of tLson into tLength
   put the long name of this stack into tStackPath
   set the itemDelimiter to slash 
   put item 2 to -2 of tStackPath into tStackPath
   set the itemDelimiter to comma
   put slash before tStackPath
   put slash & "test.lson.gz" after tStackPath
   put tLson into url("binfile:" & tStackPath)
   put the result into tResult
end mouseUp

constant kBlockChar = "█"
constant kDotChar = "•"


function imageToAsciiData pImageName
   
   local tImageData, tBytesPerPixel, tLineSize, tImageWidth, tImageHeight, tDisplayChar
   
   -- Get the image data
   put the imageData of image pImageName into tImageData
   put the width  of image pImageName into tImageWidth
   put the height of image pImageName into tImageHeight
   
   -- Each pixel is represented by 4 bytes in imageData
   put 4 into tBytesPerPixel
   
   -- Calculate line size
   put tImageWidth * tBytesPerPixel into tLineSize
   
   put kBlockChar into tDisplayChar
   
   local tBytePosition, tLineNumber, tRunsNum, tStyleA, tCurrRGB, tPrevRGB, tPathToStyle
   local tRed, tGreen, tBlue
   
   
   put "style,textColor" into tPathToStyle
   split tPathToStyle by comma
   
   --   Loop through each pixel
   repeat with y = 0 to tImageHeight - 1
      put y + 1 into tLineNumber
      put 0 into tRunsNum
      put empty into tPrevRGB
      repeat with x = 0 to tImageWidth - 1
         -- Calculate the byte position in the imageData
         
         put (y * tLineSize) + (x * tBytesPerPixel) + 1 into tBytePosition
         
         -- Extract RGB values
         -- Byte 1: Ignored (tBytePosition)
         -- Byte 2: Red (tBytePosition + 1)
         -- Byte 3: Green (tBytePosition + 2)
         -- Byte 4: Blue (tBytePosition + 3)
         put charToNum(char tBytePosition + 1 of tImageData) into tRed
         put charToNum(char tBytePosition + 2 of tImageData) into tGreen
         put charToNum(char tBytePosition + 3 of tImageData) into tBlue
         
         put tRed & comma & tGreen & comma & tBlue into tCurrRGB
         
         if tCurrRGB <> tPrevRGB then
            add 1 to tRunsNum
            put tCurrRGB into tStyleA[tLineNumber]["runs"][tRunsNum][tPathToStyle]
            put tDisplayChar after tStyleA[tLineNumber]["runs"][tRunsNum]["Text"]
            put tCurrRGB into tPrevRGB
         else
            put tDisplayChar after tStyleA[tLineNumber]["runs"][tRunsNum]["Text"]
         end if
         
      end repeat
      
   end repeat
   
   return tStyleA
end imageToAsciiData
Button 2 for loading the saved Array

Code: Select all

global gLsonDataA

on mouseUp
   local tData, tFilePath
   answer file "choose lson.gz file"
   put it into tFilePath
   if tFilePath is empty then exit mouseUp
   put url("binfile:" & tFilePath) into tData
   put decompress(tData) into tData
   put arrayDecode(tData) into tData
   put tData into gLsonDataA
end mouseUp
Note the global gLsonDataA


I changed your button "Start Animation" on the card script after adding global gLsonDataA -- stores styledText of the images to the card

Code: Select all

on startAnimation
   put kBlockChar into gDisplayChar
   if gAnimationRunning then exit  startAnimation ## avoid start when already running
   put true into gAnimationRunning
   put 0 into gCurrentFrame
   animateFrames
end startAnimation


on animateFrames
   local tStyledA, tSpeed, tDelay
   
   if not gAnimationRunning then exit animateFrames
   
   add 1 to gCurrentFrame
   
   put  gLsonDataA[gCurrentFrame] into tStyledA
   
   set the styledText field "TextArtField" of me to tStyledA
   
   local tNumElements
   put the number of elements of gLsonDataA into tNumElements
   
   if gCurrentFrame = the number of elements of gLsonDataA then put 0 into gCurrentFrame
   
   if there is a scrollbar "SpeedControl" then
      put the thumbPosition of scrollbar "SpeedControl" into tSpeed
      -- Convert speed to milliseconds (e.g., scale of 1 to 100 corresponds to 1000ms to 10ms)
      put 101 - tSpeed into tDelay
      put tDelay * 10 into tDelay -- Convert to milliseconds
   else
      put 100 into tDelay -- Default delay if scrollbar doesn't exist
   end if
   
   -- Schedule the next frame
   send "animateFrames" to me in tDelay milliseconds
   
end animateFrames

Those two handlers will start the animation by setting the styledText of the field.

It is at least as fast as your current solution but considerably easier to maintain.

If you want to test this put those two handlers above the respective handler of the same name and LC will use them. You do not even have to block the current handlers of the same name because LC only "sees" the first occurence of a handler.


Your datc format for the 12 images is 153 Kb whereas the styledText array is 190 Kb. I think that is ok for simplifying the code.

Of course this is just a suggestion, it works very well as it is.

Kind regards
Bernd

Post Reply

Return to “Games”