This is a read-only snapshot of the ComputerCraft forums, taken in April 2020.
Exerro's profile picture

Graphics & Image Api

Started by Exerro, 13 October 2013 - 09:04 AM
Exerro #1
Posted 13 October 2013 - 11:04 AM
Hi, this API is designed to help with making high quality images and animations with ease and simplicity.
It adds a new image format and a load of functions that allow you to edit these images in fairly advanced ways, and save them in multiple formats.

Current features: ( shortened )
SpoilerA new image format
Loads of unbelievably useful functions to make great looking images
Animations & animation effects to create 100 frame long animations with a couple of lines
A framebuffer
Some code examples:
Spoiler

function animation_test( )
local a = graphics.newAnimation( )
for i = 1, 40 do
  a:newFrame( 1, 1, 51, 19 )
end
a:newEffect( "ripple", 1, 40, { range = 14, x = 26, y = 10, mode = "line" }, "blue", "white", " " )
a:newEffect( "ripple", 4, 30, { range = 8, x = 37, y = 12, mode = "line" }, "red", "white", " " )
a:newEffect( "ripple", 10, 31, { range = 20, x = 4, y = 2, mode = "line" }, "yellow", "white", " " )
a:newEffect( "ripple", 10, 38, { range = 12, x = 11, y = 14, mode = "line" }, "lime", "white", " " )
a:newEffect( "slowtext", 1, 40, { speed = 0.5, text = "Hello World!", x = 1, y = 1 }, "white", "black", " " )
a:render( false, 0.05 )
end

function light_test( )
local pic = graphics.newImage( 1, 1, 51, 19 )
pic:rectangle( "fill", 1, 1, 51, 19, "white", "white", " " )
pic:rectangle( "fill", 4, 7, 20, 4, "lightBlue", "white", " " )
pic:circle( "fill", 37, 3, 4, "yellow", "white", " " )
local map = graphics.newLightMap( )
map:setMaxLightCount( 3 )
os.startTimer( 0 )
while true do
  local ev = { os.pullEvent( ) }
  if ev[1] == "mouse_click" then
   if ev[2] == 1 then
	map:newLight( ev[3], ev[4], 5 )
   else
	map:move( "set", ev[3], ev[4] )
   end
  elseif ev[1] == "mouse_scroll" then
   map:setIntensity( "add", ev[2] )
  elseif ev[1] == "timer" then
   os.startTimer( 0.1 )
  elseif ev[1] == "key" then
   return
  end
  local revert = map:applyTo( pic )
  pic:render( )
  revert( )
  map:move( "add", 1, 0 )
end
end

function fractal_test( )
local seed = 0
while true do
  term.setCursorPos( 1, 1 )
  term.setBackgroundColour( colours.white )
  term.setTextColour( colours.black )
  term.write( seed )
  local ev = { os.pullEvent( ) }
  if ev[1] == "mouse_click" then
   seed = seed + ( ev[2] == 1 and 1 or -1 )
   local i = graphics.newImage( 1, 1, 51, 19 )
   i:fractal( 1, 1, "blue", "white", " ", seed, { angle = 135, range = 60, split = 3, length = 3, continue = 2 } )
   i:render( true )
  elseif ev[1] == "key" then
   return
  end
end
end
Things I will add in the near future:
SpoilerAnimation saver
More patterns
More animation effects
Inbuilt paint program that can be called as a function
Inbuilt demos
More support for invisibility
Templates
Things I would like to add in the far future:
Spoilerpng -> TBI converter

The primary functions, creating objects and changing how the API works:
Click me!
Spoilerusing [os.loadAPI( "graphics" )] would result in the following…
image = graphics.newImage( x, y, width, height ) – creates a new image object that is [width] wide and [height] high
buffer = graphics.newFrameBuffer( x, y, width, height ) – creates a new framebuffer object that is [width] wide and [height] high
graphics.setPixelFunction( function ) – acts like term.redirect. When using image:render( ) this function will be called with [x,y,bc,tc,char]
graphics.restorePixelFunction( ) – restores the pixel function to the original one
animation = graphics.newAnimation( ) – creates a new animation object
light = graphics.newLightEntity( ) – creates a new light entity object
map = graphics.newLightMap( ) – creates a new light map object

Image functions:
Click me!
Spoilerusing [image = graphics.newImage( x, y, w, h )] would result in the following…

basic functions
image:render( force ) – renders the image, set force to true to render all pixels, false to render changed pixels
image:move( mode, x, y ) – moves the image ( only affects rendering offset ) modes = "add" / "set"
image:pixel( x, y, bc, tc, char ) – sets the image pixel at [x][y] to [bc][tc][char] bc = background colour, tc = text colour, char = character. leave bc / tc blank to keep colour at that pixel, leave char blank to set it to " " ( just render the background colour like a normal paint image )
image:write( x, y, bc, tc, … ) – writes everything in … at [x][y] with [bc],[tc] colours, has basic wrapping
image:clear( x, y, w, h, col ) – all args are optional, x, y, w, h defaults to 1, 1, width, height, col defaults to white. clears all pixels from x, y to w, h using [col] colour or white. can be used to resize image

filesystem functions
image:newImage( type, data, x, y ) – used to add new images to [image], more on this later
image:saveImage( type, path ) – used to save images in [type] format

advanced functions – all functions use [bc][tc][char] for replacing pixels
image:floodFill( x, y, bc, tc, char, value ) – flood fills based on pixels with the same [value] value as the pixel at [x][y]. for example using value = "bc" would result in all pixels with the same bc as the one at [x][y] being changed ( uses a flood filling algorithm, not all pixels are always changed depending on things on the screen )
image:line( x1, y1, x2, y2, bc, tc, char ) – draws a line from [x1][y1] to [x2][y2], x2 can be smaller than x1 and vice versa
image:rectangle( mode, x, y, w, h, bc, tc, char ) – draws a rectangle from [x][y] to [x+w-1][y+h-1], modes = "fill" and "line"
image:circle( mode, x, y, r, bc, tc, char ) – draws a circle around point [x][y] with a radius of [r], modes = "line" and "fill"
image:scale( mode, w, h ) – scales image by [w][h] i.e. scaling by [2][2] would make it 4 times larger, modes are listed later
image:newLine( dir, keep ) – adds a new line to the image, dir = "up" or "down", keep determines whether to remove lines from the opposite side to keep the height the same. true to keep, false to destroy

very advanced functions
image:pattern( mode, x, y, bc, tc, char, seed, data ) – adds a pattern to the image, more on this later
image:fractal( x, y, bc, tc, char, seed, data ) – adds a fractal to the image, bc and tc can be input as a table and it will use a different value from the table every time it draws a line
image:applyLighting( t ) – adds lighting to the image from the table of lights input, more on this at bottom

FrameBuffer functions:
Click me!
Spoilerusing [buffer = graphics.newAnimation( )] would result in the following…

use term.redirect( buffer ) to draw to the buffer

buffer would be given a set of functions ( everything in term ) that instead of drawing to the screen, draw to the window
buffer.window is an [Image], so you can use that to call [Image] functions i.e buffer.window:clear( )
buffer.data.cb is whether the cursor is blinking, but has to be applied using user code
buffer.candraw should be a boolean. in later versions, this will allow for it to draw directly to the screen as well as to the window

buffer:render( ) is coming soon, which will apply the cursor blink and let it draw to the screen if buffer.candraw is true

Animation functions:
Click me!
Spoilerusing [an = graphics.newAnimation( )] would result in the following…

frame functions
image = an:newFrame( [d] or [x, y, w, h] ) – add a frame using a pre-created image or use x, y, w, h to make a new image of that size
an:setFrame( n ) – sets the current frame to n
an:deleteFrame( n ) – removes frame[n]
currentFrame = an:nextFrame( ) – cycles through the frames
frame = an:getFrame( n ) – returns the frame[n] or current frame if n isn't given

rendering
an:render( force, time ) – force is used for rendering the frames individually, time is optional, but if given, will render all frames with a [time] sleep inbetween

other
an:newEffect( mode, sf, ef, data, bc, tc, char ) – adds a new effect from frame[sf] to frame[ef], more on effects later…

all the functions available to an [Image] can be used here also
for example:
an:rectangle( 1, 10, mode, y, x, w, h, bc, tc, char ) would add a rectangle in frames 1 to 10

LightEntity functions:
Click me!
Spoilerusing [light = graphics.newLightEntity( )] would result in the following…

light:move( mode, x, y ) – moves the light, modes = "add" and "set"
light:setIntensity( mode, level ) – sets the light intensity, modes = "add" and "set"

LightMap functions:
Click me!
Spoilerusing [map = graphics.newLightMap( )] would result in the following…

light = map:newLight( x, y, level ) – adds a new light
map:setMaxLightCount( n ) – sets the maximum amount of lights to [n], use 0 to make it infinite
map:applyTo( image ) – applies lighting to the image
map:move( mode, x, y ) – moves every light on the map, modes = "add" and "set"
map:setIntensity( mode, level ) – sets every light on the maps intensity, modes = "add" and "set"

File saving formats:
Click me!
SpoilerTBI ( old was TIF but there is another file format for images called tif ) – saves text and text colour as well as background colour
paint - a standard paint image
use "loaded" to add a loaded image to another image in newImage( )

Patterns:
Click me!
SpoilerThese are still quite buggy and nowhere near finished. I won't give very much detail, you will have to play around / look at code to fully understand how they work

give below variables to the function in the data table
speckle - creates a speckling effect around a point with a maximum radius, needs range ( radius ) and intensity ( how many speckles )
line - creates a random curvy line, needs length and degree

When using the image:pattern( ) function
the first arg should be the name of the pattern i.e. speckle or line

Fractals ( in depth ): Click me!
SpoilerHere is an image of the fractal generator in use in love2d
The green splodges are just something I added in to draw when the line stops, as you can see, it looks like some kind of plant or bush

A fractal ( in this API ) is a point and an angle that branches out to form new points and angles and connects them with lines
Think of a tree, you have the trunk, and then loads more branches branching off from other branches. This is somewhat like a fractal in this API
Fractals are one of the most advanced features of the API, so if you are looking to draw smiley faces, don't use fractals
Using large numbers can also crash your program because of stack_overflow and too_long_without_yielding, so watch out

to make a fractal, use image:fractal( x, y, bc, tc, char, seed, data )
x, y is the original point it starts from
bc, tc, char is the pixel data
seed is a random number, using the same seed and data will always result in the same fractal being generated
data is a table {
continue = the 1/[continue] chance of it carrying on, this is set to [continue] * 1.1 every time to stop infinite fractals
length = the length of the lines of the fractal
split = the maximum number of stems that appear from a point
angle = the angle of the start stem of the fractal
range = the range of the fractal i.e angle = 180, range = 20 means the rendered angle could be between 170 and 190
}

If you don't quite understand this, use the following guidelines:

set continue to about 2, 1 makes very big fractals, and can lag or crash, 1.5 is also a good one if you have a decent pc
set length to around 3-5
set split to 2 or 3
set range to about 60, the larger the range, the more blobish the fractal will look, the smaller, the more liney it will look
set angle to degrees ( between 0 and 360, 0 being up, 180 being down, 90 being right, 270 being left )

Animation effects:
Click me!
Spoilerripple - creates a circle that travels outwards from a centrepoint
sripple - creates a square that travels outwards from a centrepoint
slowtext - slow_writes text over multiple frames

variables needed to work
pass them in table format in the data arg
i.e. animation:newEffect( "ripple", 1, 10, { x = 1, y = 1, range = 10, mode = "fill" }, bc, tc, char )

ripple - x and y = centrepoint, range = maximum radius at end of animation, mode = mode required for circle generator
sripple - same as ripple
slowtext - x and y = start of text location, text = text to write, [optional] speed = characters per frame

Note:
using an animation effect changes the frames when animation:newEffect( ) is called
the effects aren't just rendering effects, they change the image itself

Lighting:
Click me!
SpoilerTo truly understand this, you have to see it in action, but basically, the API gives all colours 4 light levels when it is loaded
When you apply lighting to an image, it uses these colours to change the look of the image
The maximum radius of the lighting effect if the same as what light:setIntensity( n ) is
As it goes out, it will get darker and darker
White, lightBlue and yellow work quite well because there are lots of variations of their colour

When image:applyLighting is used, it returns a function that when called, will restore the image to what it was before

If you feel anything needs a bit more explaining, please ask in the comments or PM me. The API is so in-depth it is hard to explain everything in one post, and I am sure I have missed some things out.
If you have used / wish to use this, please comment: say what you like/dislike about the API, what I can add, what I can improve etc…
Thank you for reading

Download:
pastebin get wjQY3nCs graphics
M4sh3dP0t4t03 #2
Posted 13 October 2013 - 11:24 AM
This sounds really nice, but what about adding screenshots of the effects?
Exerro #3
Posted 13 October 2013 - 12:07 PM
This sounds really nice, but what about adding screenshots of the effects?
Sure, I'll get right on it, although you do have the fractal picture which I think looks amazing :P/>
I will edit this post with the images when I take them.

Edit: something really wierd is going on, it looks like the contents of the functions have been mixed around on pastebin…sorry for this….

Edit2: Think I have fixed it, the circle function had been replaced by the newLine function for some reason
A little lighting demo
Spoiler
Screenshot of an animation halfway through:
Spoiler
PS, the circles don't look like that normally, it's just render lag causing the bottom to render after the top as I am on a slow pc

Also, I just made a paint function to go with this, it does increase it to over 1000 lines though so I made a seperate paste for it incase you don't want it
pastebin get zcqkH2S5 graphics
Use os.loadAPI( "graphics" )
the use graphics.paint( ) to run it
Spoiler
Rougeminner #4
Posted 16 November 2013 - 08:50 PM
You think you can put a more descriptive manual to it i can not say how useful this will be but it will be AMAZING

Rougeminner
Edited on 14 July 2014 - 03:05 PM