Text Art and Animation
Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, robinmiller
Text Art and Animation
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]
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.
Re: Text Art and Animation
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:
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)
Kind regards
Bernd
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
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
Bernd
Re: Text Art and Animation
Bernd.
You are one who, like myself, would always rather play with LC than pay attention to your day job.
Craig
You are one who, like myself, would always rather play with LC than pay attention to your day job.
Craig
Re: Text Art and Animation
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
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
Re: Text Art and Animation
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.
Kind regards
Bernd
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
Re: Text Art and Animation
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
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
Re: Text Art and Animation
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:
Button 2 for loading the saved Array
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
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
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
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
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