GFXP Viewer tool

Hi all!

I made a quick tool for myself using the beautiful GFXP pattern library @ivansergeev put together. I wouldn't be surprised if something like this already exists out there, but I couldn't find an updated and user-friendly one! Crank hard, and dither!

2

I made this primarily because I realized recently that I've been spending way too much time looking at the Simulator and how it renders patterns, and not enough time seeing how it feels directly on device.

It's quite basic for now. Some potential things to add if I find use for them or others want to contribute or suggest how to:

  • More shapes and ways to better see the visual subtleties of patterns interacting
  • A basic custom pattern creator, akin to the web version Ivan has
  • Some simple implementation of the pattern animation function
  • Maybe a way to pin or select certain patterns, and display many side-by-side
  • Printing gfxp/table/setDitherPattern/binary as the web version does, to speed up workflow use when hooked to Simulator
  • A randomization function

Let me know here or add an Issue on Github if anyone has feedback or is interested in specific features !

GFXP-Viewer.pdx.zip

2 Likes

Wow! Cool! Great job!

2 Likes

Animation feature from the first version


--[[
	
	GFXP animation - creating animation from patterns.
	
	How to use

	-- step 1: import lib
		
		import 'helpers/gfxp'
		
		local gfxp = GFXP


	-- step 2: create animation instance
	-- there are two possible uses: animate a single pattern or animate multiple patterns
		
		-- multiple patterns
			-- options: 
			
				patterns = table with patterns, required
				patterns = {{0x7F, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF},
							{0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA},
							{0x0, 0x22, 0x0, 0x88, 0x0, 0x22, 0x0, 0x88}}
				
				-- or
				
				patterns = {gfxp.dots4, gfxp.gray, gfxp.darkgray}
				
				ticks = the rate of change, int, optional
				ticks = 1 -- changes will occur every frame, default option
				ticks = 2 -- changes will occur every second frame
				ticks = 10 -- changes will occur every 10 frame
				etc

			-- usage:
			
				local a = gfxp.animate:new(patterns)
				-- or
				local a = gfxp.animate:new(patterns, ticks)
			
			-- example:
				
				local a = gfxp.animate:new(gfxp.getPatterns(true))
				-- or
				local a = gfxp.animate:new({gfxp.dots4, gfxp.gray, gfxp.darkgray}, 10)


		-- single pattern
			-- options: 
				
				pattern = table with one pattern, required
				pattern = {0x0, 0xDF, 0xDF, 0xDF, 0x0, 0xFD, 0xFD, 0xFD}
				-- or
				pattern = gfxp.bricks1

				direction = direction of movement of the pattern, string, required
				direction = 'up'
				direction = 'right'
				direction = 'down'
				direction = 'left'
				
				ticks = the rate of change, int, optional
			
			-- usage:
				
				local a = gfxp.animate:new(pattern, direction, ticks)
			
			-- example:
			
				local a = gfxp.animate:new(gfxp.bricks1, 'right', 2)
				-- or
				local a = gfxp.animate:new({0x0, 0xDF, 0xDF, 0xDF, 0x0, 0xFD, 0xFD, 0xFD}, 'down')


	-- step 3: call when you update and draw some graphics
		
		-- usage:
		
			a:draw()
		
		-- example:
		
			function playdate.update()
				a:draw()
				playdate.graphics.fillRect(0, 0, 200, 120) -- for example
			end
		
		
]]--

GFXP.animate = {}
GFXP.animate.__index = GFXP.animate

function GFXP.animate:new(patterns, ...)
	
	if type(patterns) ~= 'table' or #patterns == 0 then
		error('GFXP.animate: Patterns not specified')
	end
	
	local self = {}
	setmetatable(self, metatable)

	local arg = {...}
	local mode = 1
	local ticks = 1
	local directions = {
		up = 1,
		right = 2,
		down = 3,
		left = 4
	}

	local currentDirection = 0
	local currentTicks = 0
	local currentPattern = nil
	local singleUpdateHandler = function () end

	if arg then
		
		local _type = type(patterns[1])

		if _type == 'string' or _type == 'table' then

			ticks = arg[1] and arg[1] or ticks 

		elseif _type == 'number' and #patterns == 8 or #patterns == 16 then

			mode = 2
			currentDirection = directions[arg[1]] and directions[arg[1]] or 0
			ticks = arg[2] and arg[2] or ticks
			currentPattern = patterns

			if currentDirection == 1 then

				singleUpdateHandler = function ()
					table.insert(patterns, 8, table.remove(patterns, 1))
					if #patterns == 16 then
						table.insert(patterns, 16, table.remove(patterns, 9))
					end
				end

			elseif currentDirection == 2 then
	
				singleUpdateHandler = function ()
					for i=1, #patterns do
						patterns[i] = (128 * (patterns[i] & 1)) + (patterns[i] >> 1)
					end
				end

			elseif currentDirection == 3 then

				singleUpdateHandler = function ()
					table.insert(patterns, 1, table.remove(patterns))
					if #patterns == 16 then
						table.insert(patterns, 9, table.remove(patterns, 16))
					end
				end

			elseif currentDirection == 4 then

				singleUpdateHandler = function ()
					for i=1, #patterns do
						local val = patterns[i]
						patterns[i] = (val << 1) > 255 and (val << 1) - (255 * (val >> 7)) or (val << 1) + (255 * (val >> 7))
					end
				end
			else
				error('GFXP.animate: Single pattern direction not specified')
			end
		end
	end


	function self:update()
		
		currentTicks += 1
		
		if currentTicks % ticks == 0 then

			if mode == 1 then
				self:updateMultiple()
			else
				singleUpdateHandler()
			end
		end

		if currentTicks == 1000 then
			currentTicks = 0
		end
	end


	function self:updateMultiple()
		table.insert(patterns, #patterns, table.remove(patterns, 1))
		local key, _ = next(patterns)
		currentPattern = patterns[key]
	end


	function self:draw()
		
		if not self then
			error('GFXP.animate: Call the instance')
		end

		self:update()

		if currentPattern then
			GFXP.set(currentPattern)
		end
	end

	return self
end

1 Like