Automatic outline generation

Hey!

I’m trying to make a script that listens for changes to a sprite, then updates a special layer with an outlined version of it. Here’s the code:

-------------------------------------------
-- aseprite make outline layer
--
-- Creates a layer called "Outline" if there isn't one already
-- and for each frame of the current sprite, creates a filled
-- outline of it on this layer.
-------------------------------------------

local spr = app.sprite

-- Checks for a valid sprite
if not spr then
  app.alert("There is no sprite")
  return
end


function MakeOutlines(spr)
	local self = {}
        self.spr = spr
		self.listener = nil
		
	self.create_layer = function()
		-- does the outline layer exist
		local outline_layer = nil
		for i,layer in ipairs(spr.layers) do
			if layer.name == "AutoOutline" then
				outline_layer = layer
				break
			end
		end
		if not outline_layer then
			app.command.NewLayer{name="AutoOutline"}
		end
		for i,layer in ipairs(spr.layers) do
			if layer.name == "AutoOutline" then
				outline_layer = layer
				break
			end
		end
		return outline_layer
	end	
	
	self.on_change = function(_)
		--spr.events:off(self.listener)
		local prev_layer = app.layer
		local outline_layer = nil
		for i,layer in ipairs(spr.layers) do
			if layer.name == "AutoOutline" then
				outline_layer = layer
			end
		end
		
		if not outline_layer then create_layer() end
		
		--app.command.clearCel(self.outline_layer:cel(app.frame.frameNumber))
		spr:newCel(outline_layer, app.frame.frameNumber)
		
		for i,layer in ipairs(spr.layers) do
			if layer ~= outline_layer then
				app.layer = layer
				spr.selection:selectAll()
				app.command.Copy()
				app.layer = outline_layer
				app.command.Paste()
			end
		end
		print("complete")
		spr.selection:deselect()
		
		app.layer = prev_layer
		
		--self.listener = spr.events:on('change', self.on_change)
	end
	
	self.start = function()
		self.outline_layer = self.create_layer()
		self.listener = spr.events:on('change', self.on_change)
	end
	
	return self
end

-- make an instance of it
local outliner = MakeOutlines(spr)

app.transaction(
  function()
    outliner.start()
end)

When I run this it works, that is, the current frame is copied to a new layer called AutoOutline. However, any subsequent action either crashes the app or brings up the error about being unable to modify the sprite because it’s in use by the script. “complete” is printed to the console so I know it gets out of the change callback.

What gives, anyone know?

OK, this works good enough for now:

-------------------------------------------
-- aseprite make outline layer
--
-- Creates a layer called "Outline" if there isn't one already
-- and for each frame of the current sprite, creates a filled
-- outline of it on this layer.
-------------------------------------------

local spr = app.sprite

-- Checks for a valid sprite
if not spr then
  app.alert("There is no sprite")
  return
end


function MakeOutlines(spr)
	local self = {}
        self.spr = spr
		self.listener = nil
		
	self.create_layer = function()
		-- does the outline layer exist
		local outline_layer = nil
		for i,layer in ipairs(spr.layers) do
			if layer.name == "AutoOutline" then
				outline_layer = layer
				break
			end
		end
		if not outline_layer then
			app.command.NewLayer{name="AutoOutline"}
		end
		for i,layer in ipairs(spr.layers) do
			if layer.name == "AutoOutline" then
				outline_layer = layer
				break
			end
		end
		return outline_layer
	end	
	
	self.on_change = function(_)
		local prev_layer = app.layer
		local outline_layer = nil
		for i,layer in ipairs(spr.layers) do
			if layer.name == "AutoOutline" then
				outline_layer = layer
			end
		end
		
		if not outline_layer then create_layer() end
		
		local cel = spr:newCel(outline_layer, app.frame.frameNumber)
		for i,layer in ipairs(spr.layers) do
			if layer ~= outline_layer then
				app.layer = outline_layer
				local src_cel = layer:cel(app.frame.frameNumber)
				local img = src_cel.image
				cel.image:drawImage(img, src_cel.bounds.origin)
			end
		end
		app.command.Outline{ui=false,color=Color{r=0,g=0,b=0,a=255}}
		app.layer = prev_layer
		app.refresh()
	end
	
	self.start = function()
		self.outline_layer = self.create_layer()
		self.listener = spr.events:on('change', self.on_change)
		return true
	end
	
	return self
end

-- make an instance of it
local outliner = MakeOutlines(spr)

app.transaction(
  function()
    outliner.start()
end)

Obviously needs a dialog to start/stop the service, and to generate all frame outlines if the layer doesn’t already exist etc

Bleh, it’s actually quite annoying. It doesn’t run after every pixel change, so you need to do a whole stroke before you see the result, and selections really mess with it. Also getting random “1” and “2” printed to the console. Probably better to have it as a one time script call. At least it’s on a different layer. I’d really love to see adjustment layers in the app itself. Maybe I’ll have a look at the source one day :smiley:

1 Like