{"id":13524657,"url":"https://github.com/EvineDev/Artal","last_synced_at":"2025-04-01T03:32:19.725Z","repository":{"id":129372862,"uuid":"53083334","full_name":"EvineDev/Artal","owner":"EvineDev","description":"A .PSD parsing library for LÖVE","archived":false,"fork":false,"pushed_at":"2018-04-15T15:11:00.000Z","size":92,"stargazers_count":43,"open_issues_count":0,"forks_count":3,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-24T20:57:19.376Z","etag":null,"topics":["blend-modes","game-library","lua","psd","psd-parsing-library","pure-lua","shaders","simple"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/EvineDev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-03-03T20:59:37.000Z","updated_at":"2025-03-10T02:19:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"a4705156-1f11-4c6b-b30d-50dfec3ea09d","html_url":"https://github.com/EvineDev/Artal","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EvineDev%2FArtal","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EvineDev%2FArtal/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EvineDev%2FArtal/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/EvineDev%2FArtal/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/EvineDev","download_url":"https://codeload.github.com/EvineDev/Artal/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246578511,"owners_count":20799844,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blend-modes","game-library","lua","psd","psd-parsing-library","pure-lua","shaders","simple"],"created_at":"2024-08-01T06:01:12.179Z","updated_at":"2025-04-01T03:32:19.411Z","avatar_url":"https://github.com/EvineDev.png","language":"Lua","readme":"# **Artal**\n### A .PSD parsing library for LÖVE\n\nhttps://love2d.org/\n\nPurpose is to expose the structure of .PSD files into LÖVE.\n- ImageData for the layers.\n- Names.\n- Blendmodes.\n- Clipping mode.\n- Structure, folder / image.\n\nAdobe documentation on the PSD file format: http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/\n\n![](https://a.pomf.cat/fjbcjd.gif)\n\n## artal.lua\n```lua\nartal = require(\"artal\")\npsdTable       = artal.newPSD(FileNameOrFileData)              -- full structure with the layers loaded in as images.\npsdTable       = artal.newPSD(FileNameOrFileData, \"info\")      -- full structure.\nImageDataOrNil = artal.newPSD(FileNameOrFileData, layerNumber) -- ImageData for the specified layer number.\nImageData      = artal.newPSD(FileNameOrFileData, \"composed\")\n-- ImageData of the composed image as it's stored in the psd file itself.\n-- Note that Photoshop has an slightly erroneous implementation composing the alpha into the composed image.\n-- So images without a fully opaque background will be slightly blended with white.\n```\n\n### Sample code:\n![](https://a.pomf.cat/ynqysy.png)\n```lua\nlocal artal = require(\"artal\")\nlove.graphics.setBackgroundColor(1, 1, 1)\n\nimg = artal.newPSD(\"sample.psd\")\n\nfunction love.draw()\n    for i = 1, #img do\n        love.graphics.draw(\n            img[i].image,\n            nil,       -- Position X\n            nil,       -- Position Y\n            nil,       -- Rotation\n            nil,       -- Scale X\n            nil,       -- Scale Y\n            img[i].ox, -- Offset X\n            img[i].oy) -- Offset Y\n    end\nend\n```\n\n### Full structure artal extracts from the psd.\n![](https://a.pomf.cat/aobdrd.png)\n```lua\nlocal artal = require(\"artal\")\nlove.graphics.setBackgroundColor(1, 1, 1)\n\nimg = artal.newPSD(\"sample.psd\")\n\nfunction love.draw()\n\n    -- Image info.\n    love.graphics.setColor(0, 0, 0)\n    love.graphics.print(\"Global Image info\",         0, 14 * 0)\n    love.graphics.print(\"Layer Count: \"..#img,       0, 14 * 1)\n    love.graphics.print(\"Width: \"..      img.width,  0, 14 * 2)\n    love.graphics.print(\"Height: \"..     img.height, 0, 14 * 3)\n    for i = 1, #img do\n        love.graphics.setColor(0, 0, 0)\n        love.graphics.print(\"Layer index: \"..i,                        (i - 1) * 200, 70 + 14 * 0)\n        love.graphics.print(\"name: \"..       img[i].name,              (i - 1) * 200, 70 + 14 * 1)\n        love.graphics.print(\"type: \"..       img[i].type,              (i - 1) * 200, 70 + 14 * 2)\n        love.graphics.print(\"blend: \"..      img[i].blend,             (i - 1) * 200, 70 + 14 * 3)\n        love.graphics.print(\"clip: \"..       tostring(img[i].clip),    (i - 1) * 200, 70 + 14 * 4)\n        love.graphics.print(\"ox: \"..         img[i].ox,                (i - 1) * 200, 70 + 14 * 5)\n        love.graphics.print(\"oy: \"..         img[i].oy,                (i - 1) * 200, 70 + 14 * 6)\n        love.graphics.print(\"getWidth: \"..   img[i].image:getWidth(),  (i - 1) * 200, 70 + 14 * 7)\n        love.graphics.print(\"getHeight: \"..  img[i].image:getHeight(), (i - 1) * 200, 70 + 14 * 8)\n        \n        -- Bounding Boxes\n        love.graphics.rectangle(\n            \"line\",\n            (i - 1) * 200 - img[i].ox - 0.5,\n            70 + 14 * 9   - img[i].oy - 0.5,\n            img[i].image:getWidth()  + 1,\n            img[i].image:getHeight() + 1)\n\n        love.graphics.setColor(1, 1, 1)\n        love.graphics.draw(\n            img[i].image,\n            (i - 1) * 200,\n            70 + 14 * 9,\n            nil,\n            nil,\n            nil,\n            img[i].ox,\n            img[i].oy)\n    end\n    \nend\n```\n### Layer Types\nThese are the type of layers\n```\n\"image\" = image layer with imagedata.\n\"empty\" = image layer without imagedata.\n\"open\"  = beginning of group layer.\n\"close\" = end of group layer.\n```\nLayers between an `\"open\"` and a `\"close\"` are nested inside that group.\n\n### Blend Modes\nThese are all blendmodes available.\n```\nThere's sample code for these first 5 blendmodes.\n\"norm\" = normal\n\"pass\" = pass through\n\"mul\"  = multiply\n\"scrn\" = screen\n\"over\" = overlay\n\n\"diss\" = dissolve\n\"dark\" = darken\n\"idiv\" = color burn\n\"lbrn\" = linear burn\n\"dkCl\" = darker color\n\"lite\" = lighten\n\"div\"  = color dodge\n\"lddg\" = linear dodge\n\"lgCl\" = lighter color\n\"sLit\" = soft light\n\"hLit\" = hard light\n\"vLit\" = vivid light\n\"lLit\" = linear light\n\"pLit\" = pin light\n\"hMix\" = hard mix\n\"diff\" = difference\n\"smud\" = exclusion\n\"fsub\" = subtract\n\"fdiv\" = divide\n\"hue\"  = hue\n\"sat\"  = saturation\n\"colr\" = color\n\"lum\"  = luminosity\n```\n\n### Loading specific layers.\n![](https://a.pomf.cat/uuykmu.png)\n```lua\nlocal artal = require(\"artal\")\n\nlove.graphics.setBackgroundColor(1, 1, 1)\n\nlocal fileData = love.filesystem.newFileData(\"sample.psd\")\nimg = artal.newPSD(fileData,\"info\")\n\nfor i = 1, #img do\n    if img[i].type == \"image\" and string.find(img[i].name, \"Blob\") then -- Only load layers with Blob in the name\n        img[i].image = love.graphics.newImage(artal.newPSD(fileData, i))\n    end\nend\n\nfunction love.draw()\n    for i = 1, #img do\n        if img[i].image then\n            love.graphics.draw(\n                img[i].image,\n                nil,\n                nil,\n                nil,\n                nil,\n                nil,\n                img[i].ox,\n                img[i].oy)\n        end\n    end\nend\n```\n\n### writetable.lua\nCreate a string from tables. So you can inspect tables created by artal.newPSD(). The structure below is generated from writetable.lua. And you can use that to visualize your own tables as well.\n```lua\nlocal writetable = require(\"writetable\")\ntableAsString = writetable.createStringFromTable(table)\n```\n\n```lua\n{\n    -- Table with 4 indexes, and 2 string keys.\n    -- Array values are all of type: \"table\".\n    height = 200,\n    width = 200,\n    [1] = \n    {\n        -- Table with 7 string keys.\n        oy = 0,\n        image = \"Image: 0x6b119bff80\",\n        ox = 0,\n        type = \"image\",\n        blend = \"norm\",\n        name = \"Background\",\n        clip = false,\n    },\n    [2] = \n    {\n        -- Table with 7 string keys.\n        oy = -40,\n        image = \"Image: 0x6b119c0140\",\n        ox = -68,\n        type = \"image\",\n        blend = \"norm\",\n        name = \"Red Blob\",\n        clip = false,\n    },\n    [3] = \n    {\n        -- Table with 7 string keys.\n        oy = -17,\n        image = \"Image: 0x6b119c0220\",\n        ox = -95,\n        type = \"image\",\n        blend = \"norm\",\n        name = \"Blue Blob\",\n        clip = true,\n    },\n    [4] = \n    {\n        -- Table with 7 string keys.\n        oy = -27,\n        image = \"Image: 0x6b12844c80\",\n        ox = -8,\n        type = \"image\",\n        blend = \"over\",\n        name = \"Multiple Blobs\",\n        clip = true,\n    },\n}\n```\n\n### psdShader.lua:\nNOTE: This library is very much incomplete. But it's at least a starting point for you to understand how you can create shaders that mimics photoshop effects.\n\nIt generates shaders for blending and clipping layers.\nBlendmodes implemented: Alpha, Multiply, Screen and Overlay.\n\nIt may require a \"Swap canvas\" system If you need a global blend mode. See below for samples.\n```lua\nlocal psdShader = require(\"psdShader\")\n\n-- If you pass in \"mul\", \"scrn\" or \"over\" to globalBlendmode the shader generated will require a swapcanvas.\nshaderString = psdShader.createShaderString(globalBlendmode, blendmodeBeingClipped, ...)\n\n-- Passing in canvas is only required if you use a shader with globalBlendmode: mul, scrn, over.\npsdShader.setShader(shader, canvas1, canvas2) \n\n-- Rotation and shearing not implemented. love.graphics.push() and friends does not work either.\n-- The image that is passed in is also retained. Unlike love.graphics.draw().\npsdShader.drawClip(drawOrderIndex, image, x, y, r, sx, sy, ox, oy, kx, ky) \nresultCanvas = psdShader.flatten(psdTableClipTo, psdTableBeingClipped, ...)\n```\n### Clipping sample\n![](https://a.pomf.cat/lrjlcb.png)\n```lua\nlocal artal     = require(\"artal\")\nlocal psdShader = require(\"psdShader\")\n\nlove.graphics.setBackgroundColor(1, 1, 1)\n\nimg = artal.newPSD(\"sample.psd\")\n\nlocal blendShader = {}\nblendShader.clip = love.graphics.newShader(psdShader.createShaderString(\"norm\", \"norm\", \"over\"))\n\nfunction love.draw()\n    love.graphics.draw(   img[1].image, nil, nil, nil, nil, nil, img[1].ox, img[1].oy)\n    psdShader.setShader(blendShader.clip)\n    psdShader.drawClip(1, img[3].image, nil, nil, nil, nil, nil, img[3].ox, img[3].oy)\n    psdShader.drawClip(2, img[4].image, nil, nil, nil, nil, nil, img[4].ox, img[4].oy)\n    love.graphics.draw(   img[2].image, nil, nil, nil, nil, nil, img[2].ox, img[2].oy)\n    love.graphics.setShader()\nend\n```\n\n### Blendmode sample\n![](https://a.pomf.cat/wjnszj.png)\n```lua\nlocal artal     = require(\"artal\")\nlocal psdShader = require(\"psdShader\")\nlove.graphics.setBackgroundColor(1, 1, 1)\n\nimg = artal.newPSD(\"sample.psd\")\n\nlocal blendShader = {}\nblendShader.mul  = love.graphics.newShader(psdShader.createShaderString(\"mul\"))\nblendShader.scrn = love.graphics.newShader(psdShader.createShaderString(\"scrn\"))\nblendShader.over = love.graphics.newShader(psdShader.createShaderString(\"over\"))\n\nlocal canvas = {} -- These blendmode all requires a swap canvas\ncanvas[1] = love.graphics.newCanvas(love.graphics.getDimensions())\ncanvas[2] = love.graphics.newCanvas(love.graphics.getDimensions())\n\nfunction love.draw()\n\n    love.graphics.setCanvas(canvas[1])\n    love.graphics.clear(1, 1, 1)\n\n    for i = 1, #img do\n        if  img[i].blend == \"mul\" or\n            img[i].blend == \"over\" or\n            img[i].blend == \"scrn\" then\n            \n            psdShader.setShader(blendShader[img[i].blend], canvas[1], canvas[2])\n        end\n        love.graphics.draw(img[i].image, nil, nil, nil, nil, nil, img[i].ox, img[i].oy)\n        love.graphics.setShader()\n    end\n    \n    -- Draw result to screen\n    local preCanvas = love.graphics.getCanvas()\n    love.graphics.setCanvas(nil)\n    love.graphics.setBlendMode(\"alpha\", \"premultiplied\")\n    love.graphics.draw(preCanvas)\n    love.graphics.setBlendMode(\"alpha\")\nend\n```\n### Blend and Clipping\n![](https://a.pomf.cat/klhgyg.png)\n```lua\nlocal artal     = require(\"artal\")\nlocal psdShader = require(\"psdShader\")\n\nlove.graphics.setBackgroundColor(1, 1, 1)\n\nimg = artal.newPSD(\"sample.psd\")\n\nlocal blendShader = {}\nblendShader.clipAndBlend = love.graphics.newShader(psdShader.createShaderString(\"mul\", \"over\", \"scrn\"))\n\nlocal canvas = {} -- These blendmode all requires a swap canvas\ncanvas[1] = love.graphics.newCanvas(love.graphics.getDimensions())\ncanvas[2] = love.graphics.newCanvas(love.graphics.getDimensions())\n\nfunction love.draw()\n    love.graphics.setCanvas(canvas[1])\n\n    love.graphics.clear(1, 1, 1)\n\n    love.graphics.draw(   img[1].image, nil, nil, nil, nil, nil, img[1].ox, img[1].oy)\n    psdShader.setShader(blendShader.clipAndBlend, canvas[1], canvas[2])\n    psdShader.drawClip(1, img[3].image, nil, nil, nil, nil, nil, img[3].ox, img[3].oy)\n    psdShader.drawClip(2, img[4].image, nil, nil, nil, nil, nil, img[4].ox, img[4].oy)\n    love.graphics.draw(   img[2].image, nil, nil, nil, nil, nil, img[2].ox, img[2].oy)\n    love.graphics.setShader()\n    \n    -- Draw result to screen\n    local preCanvas = love.graphics.getCanvas()\n    love.graphics.setCanvas(nil)\n    love.graphics.setBlendMode(\"alpha\", \"premultiplied\")\n    love.graphics.draw(preCanvas)\n    love.graphics.setBlendMode(\"alpha\")\nend\n```\n\n","funding_links":[],"categories":["Drawing"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FEvineDev%2FArtal","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FEvineDev%2FArtal","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FEvineDev%2FArtal/lists"}