├── .gitattributes ├── sprites ├── sTestBase │ ├── ea61c840-8a5d-429f-8833-009abe8a414a.png │ ├── layers │ │ └── ea61c840-8a5d-429f-8833-009abe8a414a │ │ │ └── 099b1ece-a9c4-4d56-9537-eaeacf89e0d1.png │ └── sTestBase.yy ├── sTestHair │ ├── ea61c840-8a5d-429f-8833-009abe8a414a.png │ ├── layers │ │ └── ea61c840-8a5d-429f-8833-009abe8a414a │ │ │ └── 099b1ece-a9c4-4d56-9537-eaeacf89e0d1.png │ └── sTestHair.yy ├── __sColorModPixel │ ├── fabd34c8-9f33-4031-8a35-e19a5fef1f46.png │ ├── layers │ │ └── fabd34c8-9f33-4031-8a35-e19a5fef1f46 │ │ │ └── 2f675806-ca23-432f-bb71-42ee681f682e.png │ └── __sColorModPixel.yy ├── sTestPaletteRows │ ├── 3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c.png │ ├── layers │ │ └── 3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c │ │ │ └── c8bb4b3f-e4ce-4289-8e2e-cbb36fa8a432.png │ └── sTestPaletteRows.yy └── sTestPaletteColumns │ ├── 3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c.png │ ├── layers │ └── 3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c │ │ └── c8bb4b3f-e4ce-4289-8e2e-cbb36fa8a432.png │ └── sTestPaletteColumns.yy ├── objects ├── oTestSprite │ ├── Step_0.gml │ ├── Create_0.gml │ ├── oTestSprite.yy │ └── Draw_0.gml └── oTestArrays │ ├── Step_0.gml │ ├── Create_0.gml │ ├── oTestArrays.yy │ └── Draw_0.gml ├── scripts ├── ColorModDestroy │ ├── ColorModDestroy.gml │ └── ColorModDestroy.yy ├── ColorMod │ ├── ColorMod.yy │ └── ColorMod.gml ├── ColorModFromSprite │ ├── ColorModFromSprite.yy │ └── ColorModFromSprite.gml ├── ColorModSpriteRowToArray │ ├── ColorModSpriteRowToArray.yy │ └── ColorModSpriteRowToArray.gml └── ColorModSpriteColumnToArray │ ├── ColorModSpriteColumnToArray.yy │ └── ColorModSpriteColumnToArray.gml ├── shaders ├── __shdColorMod │ ├── __shdColorMod.yy │ ├── __shdColorMod.vsh │ └── __shdColorMod.fsh ├── __shdColorModBlend │ ├── __shdColorModBlend.yy │ ├── __shdColorModBlend.vsh │ └── __shdColorModBlend.fsh ├── __shdColorModDebug │ ├── __shdColorModDebug.yy │ ├── __shdColorModDebug.vsh │ └── __shdColorModDebug.fsh ├── __shdColorModDebugOutput │ ├── __shdColorModDebugOutput.fsh │ ├── __shdColorModDebugOutput.yy │ └── __shdColorModDebugOutput.vsh └── __shdColorModBlendDebug │ ├── __shdColorModBlendDebug.yy │ ├── __shdColorModBlendDebug.vsh │ └── __shdColorModBlendDebug.fsh ├── options ├── main │ └── options_main.yy ├── linux │ └── options_linux.yy ├── operagx │ └── options_operagx.yy ├── html5 │ └── options_html5.yy ├── mac │ └── options_mac.yy ├── tvos │ └── options_tvos.yy ├── windows │ └── options_windows.yy ├── ios │ └── options_ios.yy └── android │ └── options_android.yy ├── LICENSE ├── ColorMod.resource_order ├── ColorMod.yyp ├── rooms └── rMain │ └── rMain.yy └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | *.yy linguist-language=Game Maker Language 2 | -------------------------------------------------------------------------------- /sprites/sTestBase/ea61c840-8a5d-429f-8833-009abe8a414a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JujuAdams/ColorMod/HEAD/sprites/sTestBase/ea61c840-8a5d-429f-8833-009abe8a414a.png -------------------------------------------------------------------------------- /sprites/sTestHair/ea61c840-8a5d-429f-8833-009abe8a414a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JujuAdams/ColorMod/HEAD/sprites/sTestHair/ea61c840-8a5d-429f-8833-009abe8a414a.png -------------------------------------------------------------------------------- /sprites/__sColorModPixel/fabd34c8-9f33-4031-8a35-e19a5fef1f46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JujuAdams/ColorMod/HEAD/sprites/__sColorModPixel/fabd34c8-9f33-4031-8a35-e19a5fef1f46.png -------------------------------------------------------------------------------- /sprites/sTestPaletteRows/3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JujuAdams/ColorMod/HEAD/sprites/sTestPaletteRows/3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c.png -------------------------------------------------------------------------------- /sprites/sTestPaletteColumns/3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JujuAdams/ColorMod/HEAD/sprites/sTestPaletteColumns/3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c.png -------------------------------------------------------------------------------- /objects/oTestSprite/Step_0.gml: -------------------------------------------------------------------------------- 1 | if (keyboard_check_pressed(ord("1"))) palette = 1; 2 | if (keyboard_check_pressed(ord("2"))) palette = 2; 3 | if (keyboard_check_pressed(ord("3"))) palette = 3; 4 | if (keyboard_check_pressed(ord("0"))) palette = 0; -------------------------------------------------------------------------------- /sprites/sTestBase/layers/ea61c840-8a5d-429f-8833-009abe8a414a/099b1ece-a9c4-4d56-9537-eaeacf89e0d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JujuAdams/ColorMod/HEAD/sprites/sTestBase/layers/ea61c840-8a5d-429f-8833-009abe8a414a/099b1ece-a9c4-4d56-9537-eaeacf89e0d1.png -------------------------------------------------------------------------------- /sprites/sTestHair/layers/ea61c840-8a5d-429f-8833-009abe8a414a/099b1ece-a9c4-4d56-9537-eaeacf89e0d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JujuAdams/ColorMod/HEAD/sprites/sTestHair/layers/ea61c840-8a5d-429f-8833-009abe8a414a/099b1ece-a9c4-4d56-9537-eaeacf89e0d1.png -------------------------------------------------------------------------------- /objects/oTestSprite/Create_0.gml: -------------------------------------------------------------------------------- 1 | //Create a ColorMod struct from the rows of pixels in a sprite 2 | //The top row is used as the default palette 3 | colorMod = ColorModFromSprite(sTestPaletteRows, 0, true); 4 | 5 | //Extra variable for the demo 6 | palette = 1; -------------------------------------------------------------------------------- /sprites/__sColorModPixel/layers/fabd34c8-9f33-4031-8a35-e19a5fef1f46/2f675806-ca23-432f-bb71-42ee681f682e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JujuAdams/ColorMod/HEAD/sprites/__sColorModPixel/layers/fabd34c8-9f33-4031-8a35-e19a5fef1f46/2f675806-ca23-432f-bb71-42ee681f682e.png -------------------------------------------------------------------------------- /sprites/sTestPaletteRows/layers/3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c/c8bb4b3f-e4ce-4289-8e2e-cbb36fa8a432.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JujuAdams/ColorMod/HEAD/sprites/sTestPaletteRows/layers/3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c/c8bb4b3f-e4ce-4289-8e2e-cbb36fa8a432.png -------------------------------------------------------------------------------- /objects/oTestArrays/Step_0.gml: -------------------------------------------------------------------------------- 1 | if (keyboard_check_pressed(ord("1"))) palette = "test 1"; 2 | if (keyboard_check_pressed(ord("2"))) palette = "test 2"; 3 | if (keyboard_check_pressed(ord("3"))) palette = "test 3"; 4 | if (keyboard_check_pressed(ord("0"))) palette = "default"; -------------------------------------------------------------------------------- /sprites/sTestPaletteColumns/layers/3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c/c8bb4b3f-e4ce-4289-8e2e-cbb36fa8a432.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JujuAdams/ColorMod/HEAD/sprites/sTestPaletteColumns/layers/3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c/c8bb4b3f-e4ce-4289-8e2e-cbb36fa8a432.png -------------------------------------------------------------------------------- /scripts/ColorModDestroy/ColorModDestroy.gml: -------------------------------------------------------------------------------- 1 | // Feather disable all 2 | 3 | /// Convenience function to destroy a ColorMod struct after making a basic safety check. 4 | /// 5 | /// @param colorMod 6 | 7 | function ColorModDestroy(_colorMod) 8 | { 9 | if (is_struct(_colorMod)) _colorMod.Destroy(); 10 | } -------------------------------------------------------------------------------- /shaders/__shdColorMod/__shdColorMod.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMShader":"", 3 | "%Name":"__shdColorMod", 4 | "name":"__shdColorMod", 5 | "parent":{ 6 | "name":"(System)", 7 | "path":"folders/ColorMod/(System).yy", 8 | }, 9 | "resourceType":"GMShader", 10 | "resourceVersion":"2.0", 11 | "type":1, 12 | } -------------------------------------------------------------------------------- /scripts/ColorMod/ColorMod.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMScript":"v1", 3 | "%Name":"ColorMod", 4 | "isCompatibility":false, 5 | "isDnD":false, 6 | "name":"ColorMod", 7 | "parent":{ 8 | "name":"ColorMod", 9 | "path":"folders/ColorMod.yy", 10 | }, 11 | "resourceType":"GMScript", 12 | "resourceVersion":"2.0", 13 | } -------------------------------------------------------------------------------- /shaders/__shdColorModBlend/__shdColorModBlend.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMShader":"", 3 | "%Name":"__shdColorModBlend", 4 | "name":"__shdColorModBlend", 5 | "parent":{ 6 | "name":"(System)", 7 | "path":"folders/ColorMod/(System).yy", 8 | }, 9 | "resourceType":"GMShader", 10 | "resourceVersion":"2.0", 11 | "type":1, 12 | } -------------------------------------------------------------------------------- /shaders/__shdColorModDebug/__shdColorModDebug.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMShader":"", 3 | "%Name":"__shdColorModDebug", 4 | "name":"__shdColorModDebug", 5 | "parent":{ 6 | "name":"(System)", 7 | "path":"folders/ColorMod/(System).yy", 8 | }, 9 | "resourceType":"GMShader", 10 | "resourceVersion":"2.0", 11 | "type":1, 12 | } -------------------------------------------------------------------------------- /scripts/ColorModDestroy/ColorModDestroy.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMScript":"v1", 3 | "%Name":"ColorModDestroy", 4 | "isCompatibility":false, 5 | "isDnD":false, 6 | "name":"ColorModDestroy", 7 | "parent":{ 8 | "name":"ColorMod", 9 | "path":"folders/ColorMod.yy", 10 | }, 11 | "resourceType":"GMScript", 12 | "resourceVersion":"2.0", 13 | } -------------------------------------------------------------------------------- /shaders/__shdColorModDebugOutput/__shdColorModDebugOutput.fsh: -------------------------------------------------------------------------------- 1 | varying vec2 v_vTexcoord; 2 | varying vec4 v_vColour; 3 | 4 | uniform float u_fColumn; 5 | uniform float u_fRow; 6 | uniform vec2 u_vTexel; 7 | 8 | void main() 9 | { 10 | gl_FragColor = v_vColour * texture2D(gm_BaseTexture, u_vTexel*vec2(u_fColumn + 0.5, u_fRow + 0.5)); 11 | } -------------------------------------------------------------------------------- /shaders/__shdColorModBlendDebug/__shdColorModBlendDebug.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMShader":"", 3 | "%Name":"__shdColorModBlendDebug", 4 | "name":"__shdColorModBlendDebug", 5 | "parent":{ 6 | "name":"(System)", 7 | "path":"folders/ColorMod/(System).yy", 8 | }, 9 | "resourceType":"GMShader", 10 | "resourceVersion":"2.0", 11 | "type":1, 12 | } -------------------------------------------------------------------------------- /shaders/__shdColorModDebugOutput/__shdColorModDebugOutput.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMShader":"", 3 | "%Name":"__shdColorModDebugOutput", 4 | "name":"__shdColorModDebugOutput", 5 | "parent":{ 6 | "name":"(System)", 7 | "path":"folders/ColorMod/(System).yy", 8 | }, 9 | "resourceType":"GMShader", 10 | "resourceVersion":"2.0", 11 | "type":1, 12 | } -------------------------------------------------------------------------------- /scripts/ColorModFromSprite/ColorModFromSprite.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMScript":"v1", 3 | "%Name":"ColorModFromSprite", 4 | "isCompatibility":false, 5 | "isDnD":false, 6 | "name":"ColorModFromSprite", 7 | "parent":{ 8 | "name":"ColorMod", 9 | "path":"folders/ColorMod.yy", 10 | }, 11 | "resourceType":"GMScript", 12 | "resourceVersion":"2.0", 13 | } -------------------------------------------------------------------------------- /scripts/ColorModSpriteRowToArray/ColorModSpriteRowToArray.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMScript":"v1", 3 | "%Name":"ColorModSpriteRowToArray", 4 | "isCompatibility":false, 5 | "isDnD":false, 6 | "name":"ColorModSpriteRowToArray", 7 | "parent":{ 8 | "name":"ColorMod", 9 | "path":"folders/ColorMod.yy", 10 | }, 11 | "resourceType":"GMScript", 12 | "resourceVersion":"2.0", 13 | } -------------------------------------------------------------------------------- /scripts/ColorModSpriteColumnToArray/ColorModSpriteColumnToArray.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMScript":"v1", 3 | "%Name":"ColorModSpriteColumnToArray", 4 | "isCompatibility":false, 5 | "isDnD":false, 6 | "name":"ColorModSpriteColumnToArray", 7 | "parent":{ 8 | "name":"ColorMod", 9 | "path":"folders/ColorMod.yy", 10 | }, 11 | "resourceType":"GMScript", 12 | "resourceVersion":"2.0", 13 | } -------------------------------------------------------------------------------- /shaders/__shdColorModDebugOutput/__shdColorModDebugOutput.vsh: -------------------------------------------------------------------------------- 1 | attribute vec3 in_Position; 2 | attribute vec4 in_Colour; 3 | attribute vec2 in_TextureCoord; 4 | 5 | varying vec2 v_vTexcoord; 6 | varying vec4 v_vColour; 7 | 8 | void main() 9 | { 10 | gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION]*vec4(in_Position.xyz, 1.0); 11 | v_vColour = in_Colour; 12 | v_vTexcoord = in_TextureCoord; 13 | } -------------------------------------------------------------------------------- /shaders/__shdColorMod/__shdColorMod.vsh: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec3 in_Position; 4 | attribute vec4 in_Colour; 5 | attribute vec2 in_TextureCoord; 6 | 7 | varying vec2 v_vTexcoord; 8 | varying vec4 v_vColour; 9 | 10 | void main() 11 | { 12 | gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION]*vec4(in_Position.xyz, 1.0); 13 | v_vColour = in_Colour; 14 | v_vTexcoord = in_TextureCoord; 15 | } -------------------------------------------------------------------------------- /shaders/__shdColorModBlend/__shdColorModBlend.vsh: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec3 in_Position; 4 | attribute vec4 in_Colour; 5 | attribute vec2 in_TextureCoord; 6 | 7 | varying vec2 v_vTexcoord; 8 | varying vec4 v_vColour; 9 | 10 | void main() 11 | { 12 | gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION]*vec4(in_Position.xyz, 1.0); 13 | v_vColour = in_Colour; 14 | v_vTexcoord = in_TextureCoord; 15 | } -------------------------------------------------------------------------------- /shaders/__shdColorModDebug/__shdColorModDebug.vsh: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec3 in_Position; 4 | attribute vec4 in_Colour; 5 | attribute vec2 in_TextureCoord; 6 | 7 | varying vec2 v_vTexcoord; 8 | varying vec4 v_vColour; 9 | 10 | void main() 11 | { 12 | gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION]*vec4(in_Position.xyz, 1.0); 13 | v_vColour = in_Colour; 14 | v_vTexcoord = in_TextureCoord; 15 | } -------------------------------------------------------------------------------- /shaders/__shdColorModBlendDebug/__shdColorModBlendDebug.vsh: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | attribute vec3 in_Position; 4 | attribute vec4 in_Colour; 5 | attribute vec2 in_TextureCoord; 6 | 7 | varying vec2 v_vTexcoord; 8 | varying vec4 v_vColour; 9 | 10 | void main() 11 | { 12 | gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION]*vec4(in_Position.xyz, 1.0); 13 | v_vColour = in_Colour; 14 | v_vTexcoord = in_TextureCoord; 15 | } -------------------------------------------------------------------------------- /options/main/options_main.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMMainOptions":"v1", 3 | "%Name":"Main", 4 | "name":"Main", 5 | "option_author":"", 6 | "option_collision_compatibility":false, 7 | "option_copy_on_write_enabled":false, 8 | "option_draw_colour":4294967295, 9 | "option_gameguid":"5a0132c1-e8c6-468e-aecc-74acb5b48ef4", 10 | "option_gameid":"0", 11 | "option_game_speed":60, 12 | "option_mips_for_3d_textures":false, 13 | "option_remove_unused_assets":false, 14 | "option_sci_usesci":false, 15 | "option_spine_licence":false, 16 | "option_steam_app_id":"0", 17 | "option_template_description":null, 18 | "option_template_icon":"${base_options_dir}/main/template_icon.png", 19 | "option_template_image":"${base_options_dir}/main/template_image.png", 20 | "option_window_colour":255, 21 | "resourceType":"GMMainOptions", 22 | "resourceVersion":"2.0", 23 | } -------------------------------------------------------------------------------- /objects/oTestArrays/Create_0.gml: -------------------------------------------------------------------------------- 1 | //Create our ColorMod struct. We need to specify all the colors that we expected to see in images 2 | //drawn with using the ColorMod palette swapper, including colors we don't want to swap 3 | colorMod = new ColorMod(ColorModSpriteRowToArray(sTestPaletteRows, 0, 0)); 4 | 5 | //Add a default pass-through palette for testing 6 | //Using an empty array will cause ColorMod to not transform the colors at all 7 | colorMod.PaletteAdd("default", []); 8 | 9 | //Add our first test palette 10 | colorMod.PaletteAdd("test 1", ColorModSpriteRowToArray(sTestPaletteRows, 0, 1)); 11 | 12 | //Add a couple more palettes for testing 13 | colorMod.PaletteAdd("test 2", ColorModSpriteRowToArray(sTestPaletteRows, 0, 2)); 14 | 15 | colorMod.PaletteAdd("test 3", ColorModSpriteRowToArray(sTestPaletteRows, 0, 3)); 16 | 17 | //Extra variable for the demo 18 | palette = "test 1"; -------------------------------------------------------------------------------- /options/linux/options_linux.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMLinuxOptions":"", 3 | "%Name":"Linux", 4 | "name":"Linux", 5 | "option_linux_allow_fullscreen":false, 6 | "option_linux_disable_sandbox":false, 7 | "option_linux_display_cursor":true, 8 | "option_linux_display_name":"Created with GameMaker", 9 | "option_linux_display_splash":false, 10 | "option_linux_enable_steam":false, 11 | "option_linux_homepage":"http://www.yoyogames.com", 12 | "option_linux_icon":"${base_options_dir}/linux/icons/64.png", 13 | "option_linux_interpolate_pixels":true, 14 | "option_linux_long_desc":"", 15 | "option_linux_maintainer_email":"", 16 | "option_linux_resize_window":false, 17 | "option_linux_scale":0, 18 | "option_linux_short_desc":"", 19 | "option_linux_splash_screen":"${base_options_dir}/linux/splash/splash.png", 20 | "option_linux_start_fullscreen":false, 21 | "option_linux_sync":false, 22 | "option_linux_texture_page":"2048x2048", 23 | "option_linux_version":"1.0.0.0", 24 | "resourceType":"GMLinuxOptions", 25 | "resourceVersion":"2.0", 26 | } -------------------------------------------------------------------------------- /options/operagx/options_operagx.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMOperaGXOptions":"", 3 | "%Name":"operagx", 4 | "name":"operagx", 5 | "option_operagx_display_cursor":true, 6 | "option_operagx_editUrl":"", 7 | "option_operagx_game_name":"${project_name}", 8 | "option_operagx_guid":"", 9 | "option_operagx_internalShareUrl":"", 10 | "option_operagx_interpolate_pixels":true, 11 | "option_operagx_mod_editUrl":"", 12 | "option_operagx_mod_game_name":"${project_name}", 13 | "option_operagx_mod_guid":"", 14 | "option_operagx_mod_internalShareUrl":"", 15 | "option_operagx_mod_next_version":"1.0.0.0", 16 | "option_operagx_mod_publicShareUrl":"", 17 | "option_operagx_mod_team_id":"", 18 | "option_operagx_mod_team_name":"", 19 | "option_operagx_mod_version":"1.0.0.0", 20 | "option_operagx_next_version":"1.0.0.0", 21 | "option_operagx_publicShareUrl":"", 22 | "option_operagx_scale":0, 23 | "option_operagx_team_id":"", 24 | "option_operagx_team_name":"", 25 | "option_operagx_texture_page":"2048x2048", 26 | "option_operagx_version":"1.0.0.0", 27 | "resourceType":"GMOperaGXOptions", 28 | "resourceVersion":"2.0", 29 | } -------------------------------------------------------------------------------- /shaders/__shdColorMod/__shdColorMod.fsh: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying vec2 v_vTexcoord; 4 | varying vec4 v_vColour; 5 | 6 | uniform sampler2D u_sPalette; 7 | uniform float u_fColumn; 8 | uniform vec4 u_vModulo; 9 | uniform vec2 u_vTexel; 10 | 11 | //Special integer modulo function because GLSE ES 1.00 doesn't have it 12 | float modI(float a, float b) 13 | { 14 | float m = a - b*floor((a + 0.5) / b); 15 | return floor(m + 0.5); 16 | } 17 | 18 | vec3 modV(vec3 a, float b) 19 | { 20 | vec3 m = a - b*floor((a + 0.5) / b); 21 | return floor(m + 0.5); 22 | } 23 | 24 | void main() 25 | { 26 | vec4 inputSample = texture2D(gm_BaseTexture, v_vTexcoord); 27 | 28 | vec3 moduloVector = u_vModulo.rgb*modV(255.0*inputSample.rgb, u_vModulo.a); 29 | float moduloValue = mod(moduloVector.r + moduloVector.g + moduloVector.b, u_vModulo.a); 30 | vec4 outputSample = texture2D(u_sPalette, u_vTexel*vec2(u_fColumn + 0.5, moduloValue + 0.5)); 31 | 32 | gl_FragColor.rgb = v_vColour.rgb * mix(inputSample.rgb, outputSample.rgb, outputSample.a); 33 | gl_FragColor.a = v_vColour.a * inputSample.a; 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Juju Adams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/ColorModSpriteRowToArray/ColorModSpriteRowToArray.gml: -------------------------------------------------------------------------------- 1 | // Feather disable all 2 | 3 | /// Helper function to convert a row of pixels in a sprite into an array of 24-bit RGB colour 4 | /// values. 5 | /// 6 | /// @param sprite 7 | /// @param image 8 | /// @param y 9 | 10 | function ColorModSpriteRowToArray(_sprite, _image, _y) 11 | { 12 | var _width = sprite_get_width(_sprite); 13 | var _height = sprite_get_height(_sprite); 14 | 15 | var _array = array_create(_width, 0x000000); 16 | 17 | var _surface = surface_create(_width, _height); 18 | surface_set_target(_surface); 19 | draw_sprite(_sprite, _image, sprite_get_xoffset(_sprite), sprite_get_yoffset(_sprite)); 20 | surface_reset_target(); 21 | 22 | var _buffer = buffer_create(4*_width*_height, buffer_fixed, 1); 23 | buffer_get_surface(_buffer, _surface, 0); 24 | surface_free(_surface); 25 | 26 | buffer_seek(_buffer, buffer_seek_start, 4*_width*_y); 27 | 28 | var _i = 0; 29 | repeat(_width) 30 | { 31 | _array[_i] = 0xFFFFFF & buffer_read(_buffer, buffer_u32); 32 | ++_i; 33 | } 34 | 35 | buffer_delete(_buffer); 36 | 37 | return _array; 38 | } -------------------------------------------------------------------------------- /scripts/ColorModSpriteColumnToArray/ColorModSpriteColumnToArray.gml: -------------------------------------------------------------------------------- 1 | // Feather disable all 2 | 3 | /// Helper function to convert a column of pixels in a sprite into an array of 24-bit RGB colour 4 | /// values. 5 | /// 6 | /// @param sprite 7 | /// @param image 8 | /// @param x 9 | 10 | function ColorModSpriteColumnToArray(_sprite, _image, _x) 11 | { 12 | var _width = sprite_get_width(_sprite); 13 | var _height = sprite_get_height(_sprite); 14 | 15 | var _array = array_create(_height, 0x000000); 16 | 17 | var _surface = surface_create(_width, _height); 18 | surface_set_target(_surface); 19 | draw_sprite(_sprite, _image, sprite_get_xoffset(_sprite), sprite_get_yoffset(_sprite)); 20 | surface_reset_target(); 21 | 22 | var _buffer = buffer_create(4*_width*_height, buffer_fixed, 1); 23 | buffer_get_surface(_buffer, _surface, 0); 24 | surface_free(_surface); 25 | 26 | buffer_seek(_buffer, buffer_seek_start, 4*_width*_x); 27 | 28 | var _i = 0; 29 | var _byte = 4*_x; 30 | repeat(_height) 31 | { 32 | _array[_i] = 0xFFFFFF & buffer_peek(_buffer, _byte, buffer_u32); 33 | 34 | ++_i; 35 | _byte += 4*_width; 36 | } 37 | 38 | buffer_delete(_buffer); 39 | 40 | return _array; 41 | } -------------------------------------------------------------------------------- /shaders/__shdColorModBlend/__shdColorModBlend.fsh: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying vec2 v_vTexcoord; 4 | varying vec4 v_vColour; 5 | 6 | uniform sampler2D u_sPalette; 7 | uniform vec3 u_vColumnData; 8 | uniform float u_fBlend; 9 | uniform vec4 u_vModulo; 10 | uniform vec2 u_vTexel; 11 | 12 | //Special integer modulo function because GLSE ES 1.00 doesn't have it 13 | float modI(float a, float b) 14 | { 15 | float m = a - b*floor((a + 0.5) / b); 16 | return floor(m + 0.5); 17 | } 18 | 19 | vec3 modV(vec3 a, float b) 20 | { 21 | vec3 m = a - b*floor((a + 0.5) / b); 22 | return floor(m + 0.5); 23 | } 24 | 25 | void main() 26 | { 27 | vec4 inputSample = texture2D(gm_BaseTexture, v_vTexcoord); 28 | 29 | vec3 moduloVector = u_vModulo.rgb*modV(255.0*inputSample.rgb, u_vModulo.a); 30 | float moduloValue = mod(moduloVector.r + moduloVector.g + moduloVector.b, u_vModulo.a); 31 | vec4 outputSample = mix(texture2D(u_sPalette, u_vTexel*vec2(u_vColumnData.x + 0.5, moduloValue + 0.5)), 32 | texture2D(u_sPalette, u_vTexel*vec2(u_vColumnData.y + 0.5, moduloValue + 0.5)), 33 | u_vColumnData.z); 34 | 35 | gl_FragColor.rgb = v_vColour.rgb * mix(inputSample.rgb, outputSample.rgb, outputSample.a); 36 | gl_FragColor.a = v_vColour.a * inputSample.a; 37 | } -------------------------------------------------------------------------------- /objects/oTestArrays/oTestArrays.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMObject":"", 3 | "%Name":"oTestArrays", 4 | "eventList":[ 5 | {"$GMEvent":"v1","%Name":"","collisionObjectId":null,"eventNum":0,"eventType":0,"isDnD":false,"name":"","resourceType":"GMEvent","resourceVersion":"2.0",}, 6 | {"$GMEvent":"v1","%Name":"","collisionObjectId":null,"eventNum":0,"eventType":8,"isDnD":false,"name":"","resourceType":"GMEvent","resourceVersion":"2.0",}, 7 | {"$GMEvent":"v1","%Name":"","collisionObjectId":null,"eventNum":0,"eventType":3,"isDnD":false,"name":"","resourceType":"GMEvent","resourceVersion":"2.0",}, 8 | ], 9 | "managed":true, 10 | "name":"oTestArrays", 11 | "overriddenProperties":[], 12 | "parent":{ 13 | "name":"ColorMod", 14 | "path":"ColorMod.yyp", 15 | }, 16 | "parentObjectId":null, 17 | "persistent":false, 18 | "physicsAngularDamping":0.1, 19 | "physicsDensity":0.5, 20 | "physicsFriction":0.2, 21 | "physicsGroup":1, 22 | "physicsKinematic":false, 23 | "physicsLinearDamping":0.1, 24 | "physicsObject":false, 25 | "physicsRestitution":0.1, 26 | "physicsSensor":false, 27 | "physicsShape":1, 28 | "physicsShapePoints":[], 29 | "physicsStartAwake":true, 30 | "properties":[], 31 | "resourceType":"GMObject", 32 | "resourceVersion":"2.0", 33 | "solid":false, 34 | "spriteId":null, 35 | "spriteMaskId":null, 36 | "visible":true, 37 | } -------------------------------------------------------------------------------- /objects/oTestSprite/oTestSprite.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMObject":"", 3 | "%Name":"oTestSprite", 4 | "eventList":[ 5 | {"$GMEvent":"v1","%Name":"","collisionObjectId":null,"eventNum":0,"eventType":0,"isDnD":false,"name":"","resourceType":"GMEvent","resourceVersion":"2.0",}, 6 | {"$GMEvent":"v1","%Name":"","collisionObjectId":null,"eventNum":0,"eventType":8,"isDnD":false,"name":"","resourceType":"GMEvent","resourceVersion":"2.0",}, 7 | {"$GMEvent":"v1","%Name":"","collisionObjectId":null,"eventNum":0,"eventType":3,"isDnD":false,"name":"","resourceType":"GMEvent","resourceVersion":"2.0",}, 8 | ], 9 | "managed":true, 10 | "name":"oTestSprite", 11 | "overriddenProperties":[], 12 | "parent":{ 13 | "name":"ColorMod", 14 | "path":"ColorMod.yyp", 15 | }, 16 | "parentObjectId":null, 17 | "persistent":false, 18 | "physicsAngularDamping":0.1, 19 | "physicsDensity":0.5, 20 | "physicsFriction":0.2, 21 | "physicsGroup":1, 22 | "physicsKinematic":false, 23 | "physicsLinearDamping":0.1, 24 | "physicsObject":false, 25 | "physicsRestitution":0.1, 26 | "physicsSensor":false, 27 | "physicsShape":1, 28 | "physicsShapePoints":[], 29 | "physicsStartAwake":true, 30 | "properties":[], 31 | "resourceType":"GMObject", 32 | "resourceVersion":"2.0", 33 | "solid":false, 34 | "spriteId":null, 35 | "spriteMaskId":null, 36 | "visible":true, 37 | } -------------------------------------------------------------------------------- /options/html5/options_html5.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMHtml5Options":"", 3 | "%Name":"HTML5", 4 | "name":"HTML5", 5 | "option_html5_allow_fullscreen":true, 6 | "option_html5_browser_title":"Created with GameMaker", 7 | "option_html5_centregame":false, 8 | "option_html5_display_cursor":true, 9 | "option_html5_facebook_app_display_name":"", 10 | "option_html5_facebook_id":"", 11 | "option_html5_flurry_enable":false, 12 | "option_html5_flurry_id":"", 13 | "option_html5_foldername":"html5game", 14 | "option_html5_google_analytics_enable":false, 15 | "option_html5_google_tracking_id":"", 16 | "option_html5_icon":"${base_options_dir}/html5/fav.ico", 17 | "option_html5_index":"", 18 | "option_html5_interpolate_pixels":true, 19 | "option_html5_jsprepend":"", 20 | "option_html5_loadingbar":"", 21 | "option_html5_localrunalert":true, 22 | "option_html5_outputdebugtoconsole":true, 23 | "option_html5_outputname":"index.html", 24 | "option_html5_scale":0, 25 | "option_html5_splash_png":"${base_options_dir}/html5/splash.png", 26 | "option_html5_texture_page":"2048x2048", 27 | "option_html5_usebuiltinfont":true, 28 | "option_html5_usebuiltinparticles":true, 29 | "option_html5_usesplash":false, 30 | "option_html5_use_facebook":false, 31 | "option_html5_version":"1.0.0.0", 32 | "option_html5_webgl":2, 33 | "resourceType":"GMHtml5Options", 34 | "resourceVersion":"2.0", 35 | } -------------------------------------------------------------------------------- /shaders/__shdColorModDebug/__shdColorModDebug.fsh: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying vec2 v_vTexcoord; 4 | varying vec4 v_vColour; 5 | 6 | uniform sampler2D u_sPalette; 7 | uniform float u_fColumn; 8 | uniform vec4 u_vModulo; 9 | uniform vec2 u_vTexel; 10 | 11 | //Special integer modulo function because GLSE ES 1.00 doesn't have it 12 | float modI(float a, float b) 13 | { 14 | float m = a - b*floor((a + 0.5) / b); 15 | return floor(m + 0.5); 16 | } 17 | 18 | vec3 modV(vec3 a, float b) 19 | { 20 | vec3 m = a - b*floor((a + 0.5) / b); 21 | return floor(m + 0.5); 22 | } 23 | 24 | void main() 25 | { 26 | vec4 inputSample = texture2D(gm_BaseTexture, v_vTexcoord); 27 | 28 | vec3 moduloVector = u_vModulo.rgb*modV(255.0*inputSample.rgb, u_vModulo.a); 29 | float moduloValue = mod(moduloVector.r + moduloVector.g + moduloVector.b, u_vModulo.a); 30 | 31 | vec4 testSample = texture2D(u_sPalette, u_vTexel*vec2(0.5, moduloValue + 0.5)); 32 | if (all(equal(testSample.rgb, inputSample.rgb))) 33 | { 34 | vec4 outputSample = texture2D(u_sPalette, u_vTexel*vec2(u_fColumn + 0.5, moduloValue + 0.5)); 35 | gl_FragColor.rgb = v_vColour.rgb * mix(inputSample.rgb, outputSample.rgb, outputSample.a); 36 | gl_FragColor.a = v_vColour.a * inputSample.a; 37 | } 38 | else 39 | { 40 | gl_FragColor = vec4(1.0, 0.0, 1.0, inputSample.a); 41 | } 42 | } -------------------------------------------------------------------------------- /options/mac/options_mac.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMMacOptions":"", 3 | "%Name":"macOS", 4 | "name":"macOS", 5 | "option_mac_allow_fullscreen":false, 6 | "option_mac_allow_incoming_network":false, 7 | "option_mac_allow_outgoing_network":false, 8 | "option_mac_apple_sign_in":false, 9 | "option_mac_app_category":"Games", 10 | "option_mac_app_id":"com.company.game", 11 | "option_mac_arm64":true, 12 | "option_mac_build_app_store":false, 13 | "option_mac_build_number":0, 14 | "option_mac_copyright":"", 15 | "option_mac_disable_sandbox":false, 16 | "option_mac_display_cursor":true, 17 | "option_mac_display_name":"Created with GameMaker", 18 | "option_mac_enable_retina":false, 19 | "option_mac_enable_steam":false, 20 | "option_mac_icon_png":"${base_options_dir}/mac/icons/1024.png", 21 | "option_mac_installer_background_png":"${base_options_dir}/mac/splash/installer_background.png", 22 | "option_mac_interpolate_pixels":true, 23 | "option_mac_menu_dock":false, 24 | "option_mac_min_version":"10.10", 25 | "option_mac_output_dir":"~/gamemakerstudio2", 26 | "option_mac_resize_window":false, 27 | "option_mac_scale":0, 28 | "option_mac_signing_identity":"Developer ID Application:", 29 | "option_mac_splash_png":"${base_options_dir}/mac/splash/splash.png", 30 | "option_mac_start_fullscreen":false, 31 | "option_mac_team_id":"", 32 | "option_mac_texture_page":"2048x2048", 33 | "option_mac_version":"1.0.0.0", 34 | "option_mac_vsync":false, 35 | "option_mac_x86_64":true, 36 | "resourceType":"GMMacOptions", 37 | "resourceVersion":"2.0", 38 | } -------------------------------------------------------------------------------- /options/tvos/options_tvos.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMtvOSOptions":"v1", 3 | "%Name":"tvOS", 4 | "name":"tvOS", 5 | "option_tvos_build_number":0, 6 | "option_tvos_bundle_name":"com.company.game", 7 | "option_tvos_display_cursor":false, 8 | "option_tvos_display_name":"Made in GameMaker", 9 | "option_tvos_enable_broadcast":false, 10 | "option_tvos_icon_1280":"${base_options_dir}/tvos/icons/1280.png", 11 | "option_tvos_icon_400":"${base_options_dir}/tvos/icons/400.png", 12 | "option_tvos_icon_400_2x":"${base_options_dir}/tvos/icons/400_2x.png", 13 | "option_tvos_interpolate_pixels":true, 14 | "option_tvos_min_version":"10.0", 15 | "option_tvos_output_dir":"~/GameMakerStudio2/tvOS", 16 | "option_tvos_podfile_lock_path":"${options_dir}\\tvos\\Podfile.lock", 17 | "option_tvos_podfile_path":"${options_dir}\\tvos\\Podfile", 18 | "option_tvos_scale":0, 19 | "option_tvos_splashscreen":"${base_options_dir}/tvos/splash/splash.png", 20 | "option_tvos_splashscreen_2x":"${base_options_dir}/tvos/splash/splash_2x.png", 21 | "option_tvos_splash_time":0, 22 | "option_tvos_team_id":"", 23 | "option_tvos_texture_page":"2048x2048", 24 | "option_tvos_topshelf":"${base_options_dir}/tvos/topshelf/topshelf.png", 25 | "option_tvos_topshelf_2x":"${base_options_dir}/tvos/topshelf/topshelf_2x.png", 26 | "option_tvos_topshelf_wide":"${base_options_dir}/tvos/topshelf/topshelf_wide.png", 27 | "option_tvos_topshelf_wide_2x":"${base_options_dir}/tvos/topshelf/topshelf_wide_2x.png", 28 | "option_tvos_version":"1.0.0.0", 29 | "resourceType":"GMtvOSOptions", 30 | "resourceVersion":"2.0", 31 | } -------------------------------------------------------------------------------- /shaders/__shdColorModBlendDebug/__shdColorModBlendDebug.fsh: -------------------------------------------------------------------------------- 1 | precision highp float; 2 | 3 | varying vec2 v_vTexcoord; 4 | varying vec4 v_vColour; 5 | 6 | uniform sampler2D u_sPalette; 7 | uniform vec3 u_vColumnData; 8 | uniform vec4 u_vModulo; 9 | uniform vec2 u_vTexel; 10 | 11 | //Special integer modulo function because GLSE ES 1.00 doesn't have it 12 | float modI(float a, float b) 13 | { 14 | float m = a - b*floor((a + 0.5) / b); 15 | return floor(m + 0.5); 16 | } 17 | 18 | vec3 modV(vec3 a, float b) 19 | { 20 | vec3 m = a - b*floor((a + 0.5) / b); 21 | return floor(m + 0.5); 22 | } 23 | 24 | void main() 25 | { 26 | vec4 inputSample = texture2D(gm_BaseTexture, v_vTexcoord); 27 | 28 | vec3 moduloVector = u_vModulo.rgb*modV(255.0*inputSample.rgb, u_vModulo.a); 29 | float moduloValue = mod(moduloVector.r + moduloVector.g + moduloVector.b, u_vModulo.a); 30 | 31 | vec4 testSample = texture2D(u_sPalette, u_vTexel*vec2(0.5, moduloValue + 0.5)); 32 | if (all(equal(testSample.rgb, inputSample.rgb))) 33 | { 34 | vec4 outputSample = mix(texture2D(u_sPalette, u_vTexel*vec2(u_vColumnData.x + 0.5, moduloValue + 0.5)), 35 | texture2D(u_sPalette, u_vTexel*vec2(u_vColumnData.y + 0.5, moduloValue + 0.5)), 36 | u_vColumnData.z); 37 | 38 | gl_FragColor.rgb = v_vColour.rgb * mix(inputSample.rgb, outputSample.rgb, outputSample.a); 39 | gl_FragColor.a = v_vColour.a * inputSample.a; 40 | } 41 | else 42 | { 43 | gl_FragColor = vec4(1.0, 0.0, 1.0, inputSample.a); 44 | } 45 | } -------------------------------------------------------------------------------- /options/windows/options_windows.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMWindowsOptions":"", 3 | "%Name":"Windows", 4 | "name":"Windows", 5 | "option_windows_allow_fullscreen_switching":false, 6 | "option_windows_borderless":false, 7 | "option_windows_company_info":"Juju Adams", 8 | "option_windows_copyright_info":"Juju Adams (c) 2024", 9 | "option_windows_copy_exe_to_dest":false, 10 | "option_windows_description_info":"ColorMod", 11 | "option_windows_disable_sandbox":false, 12 | "option_windows_display_cursor":true, 13 | "option_windows_display_name":"ColorMod", 14 | "option_windows_enable_steam":false, 15 | "option_windows_executable_name":"${project_name}.exe", 16 | "option_windows_icon":"${base_options_dir}/windows/icons/icon.ico", 17 | "option_windows_installer_finished":"${base_options_dir}/windows/installer/finished.bmp", 18 | "option_windows_installer_header":"${base_options_dir}/windows/installer/header.bmp", 19 | "option_windows_interpolate_pixels":false, 20 | "option_windows_license":"${base_options_dir}/windows/installer/license.txt", 21 | "option_windows_nsis_file":"${base_options_dir}/windows/installer/nsis_script.nsi", 22 | "option_windows_product_info":"ColorMod", 23 | "option_windows_resize_window":false, 24 | "option_windows_save_location":0, 25 | "option_windows_scale":0, 26 | "option_windows_sleep_margin":10, 27 | "option_windows_splash_screen":"${base_options_dir}/windows/splash/splash.png", 28 | "option_windows_start_fullscreen":false, 29 | "option_windows_steam_use_alternative_launcher":false, 30 | "option_windows_texture_page":"2048x2048", 31 | "option_windows_use_splash":false, 32 | "option_windows_version":"1.3.0.0", 33 | "option_windows_vsync":false, 34 | "resourceType":"GMWindowsOptions", 35 | "resourceVersion":"2.0", 36 | } -------------------------------------------------------------------------------- /ColorMod.resource_order: -------------------------------------------------------------------------------- 1 | { 2 | "FolderOrderSettings":[ 3 | {"name":"(System)","order":6,"path":"folders/ColorMod/(System).yy",}, 4 | ], 5 | "ResourceOrderSettings":[ 6 | {"name":"oTestArrays","order":2,"path":"objects/oTestArrays/oTestArrays.yy",}, 7 | {"name":"oTestSprite","order":1,"path":"objects/oTestSprite/oTestSprite.yy",}, 8 | {"name":"rMain","order":3,"path":"rooms/rMain/rMain.yy",}, 9 | {"name":"ColorMod","order":1,"path":"scripts/ColorMod/ColorMod.yy",}, 10 | {"name":"ColorModDestroy","order":3,"path":"scripts/ColorModDestroy/ColorModDestroy.yy",}, 11 | {"name":"ColorModFromSprite","order":2,"path":"scripts/ColorModFromSprite/ColorModFromSprite.yy",}, 12 | {"name":"ColorModSpriteColumnToArray","order":5,"path":"scripts/ColorModSpriteColumnToArray/ColorModSpriteColumnToArray.yy",}, 13 | {"name":"ColorModSpriteRowToArray","order":4,"path":"scripts/ColorModSpriteRowToArray/ColorModSpriteRowToArray.yy",}, 14 | {"name":"__shdColorModBlend","order":3,"path":"shaders/__shdColorModBlend/__shdColorModBlend.yy",}, 15 | {"name":"__shdColorModBlendDebug","order":4,"path":"shaders/__shdColorModBlendDebug/__shdColorModBlendDebug.yy",}, 16 | {"name":"__shdColorModDebug","order":1,"path":"shaders/__shdColorModDebug/__shdColorModDebug.yy",}, 17 | {"name":"__shdColorModDebugOutput","order":2,"path":"shaders/__shdColorModDebugOutput/__shdColorModDebugOutput.yy",}, 18 | {"name":"__sColorModPixel","order":5,"path":"sprites/__sColorModPixel/__sColorModPixel.yy",}, 19 | {"name":"sTestBase","order":4,"path":"sprites/sTestBase/sTestBase.yy",}, 20 | {"name":"sTestHair","order":5,"path":"sprites/sTestHair/sTestHair.yy",}, 21 | {"name":"sTestPaletteColumns","order":7,"path":"sprites/sTestPaletteColumns/sTestPaletteColumns.yy",}, 22 | {"name":"sTestPaletteRows","order":6,"path":"sprites/sTestPaletteRows/sTestPaletteRows.yy",}, 23 | ], 24 | } -------------------------------------------------------------------------------- /objects/oTestArrays/Draw_0.gml: -------------------------------------------------------------------------------- 1 | var _backgroundAlpha = 0.3; 2 | 3 | //Base sprite 4 | var _x = (room_width div 3) - 100; 5 | draw_sprite_ext(sTestBase, 0, _x, 400, 10, 10, 0, c_white, 1); 6 | draw_sprite_ext(sTestHair, 0, _x, 400, 10, 10, 0, c_white, 1); 7 | 8 | //Palette swapped sprite 9 | var _x = 2*(room_width div 3) - 100; 10 | colorMod.SetShader(palette); 11 | draw_sprite_ext(sTestBase, 0, _x, 400, 10, 10, 0, c_white, 1); 12 | draw_sprite_ext(sTestHair, 0, _x, 400, 10, 10, 0, c_white, 1); 13 | shader_reset(); 14 | 15 | //Draw a visualisation for the palette surface 16 | var _x = room_width - 110 17 | var _y = 15; 18 | var _scale = 8; 19 | draw_set_color(c_ltgray); 20 | draw_set_alpha(_backgroundAlpha); 21 | draw_rectangle(_x - 5, _y - 5, _x + _scale*colorMod.PaletteCount() + 5, _y + _scale*colorMod.GetModulo() + 5, false); 22 | colorMod.DebugDrawPalette(_x, _y, _scale); 23 | 24 | //Info text 25 | var _string = ""; 26 | _string += "ColorMod - Fast palette swapper\n"; 27 | _string += "Juju Adams 2024\n"; 28 | _string += "\n"; 29 | _string += "Press 1/2/3/0 to change palette\n"; 30 | 31 | var _width = string_width(_string); 32 | var _height = string_height(_string); 33 | 34 | var _x = 10; 35 | 36 | draw_set_color(c_black); 37 | draw_set_alpha(_backgroundAlpha); 38 | draw_rectangle(_x, 10, _x + _width + 20, 10 + _height + 20, false); 39 | 40 | draw_text(_x + 10, 21, _string); 41 | draw_set_color(c_white); 42 | draw_set_alpha(1); 43 | draw_text(_x + 10, 20, _string); 44 | 45 | //Info text 46 | var _string = ""; 47 | _string += "This is the actual surface used for color data --->\n"; 48 | _string += "Each column is a palette\n"; 49 | _string += "Each row is a replacement colour\n"; 50 | 51 | var _width = string_width(_string); 52 | var _height = string_height(_string); 53 | 54 | var _x = room_width - 150 - _width; 55 | 56 | draw_set_color(c_black); 57 | draw_set_alpha(_backgroundAlpha); 58 | draw_rectangle(_x, 10, _x + _width + 20, 10 + _height + 20, false); 59 | 60 | draw_text(_x + 10, 21, _string); 61 | draw_set_color(c_white); 62 | draw_set_alpha(1); 63 | draw_text(_x + 10, 20, _string); -------------------------------------------------------------------------------- /objects/oTestSprite/Draw_0.gml: -------------------------------------------------------------------------------- 1 | var _backgroundAlpha = 0.3; 2 | 3 | //Base sprite 4 | var _x = (room_width div 3) - 100; 5 | draw_sprite_ext(sTestBase, 0, _x, 400, 10, 10, 0, c_white, 1); 6 | draw_sprite_ext(sTestHair, 0, _x, 400, 10, 10, 0, c_white, 1); 7 | 8 | //Palette swapped sprite 9 | var _x = 2*(room_width div 3) - 100; 10 | colorMod.SetShader(palette); 11 | draw_sprite_ext(sTestBase, 0, _x, 400, 10, 10, 0, c_white, 1); 12 | draw_sprite_ext(sTestHair, 0, _x, 400, 10, 10, 0, c_white, 1); 13 | shader_reset(); 14 | 15 | //Draw a visualisation for the palette surface 16 | var _x = room_width - 110 17 | var _y = 15; 18 | var _scale = 8; 19 | draw_set_color(c_ltgray); 20 | draw_set_alpha(_backgroundAlpha); 21 | draw_rectangle(_x - 5, _y - 5, _x + _scale*colorMod.PaletteCount() + 5, _y + _scale*colorMod.GetModulo() + 5, false); 22 | colorMod.DebugDrawPalette(_x, _y, _scale); 23 | 24 | //Info text 25 | var _string = ""; 26 | _string += "ColorMod - Fast palette swapper\n"; 27 | _string += "Juju Adams 2024\n"; 28 | _string += "\n"; 29 | _string += "Press 1/2/3/0 to change palette\n"; 30 | 31 | var _width = string_width(_string); 32 | var _height = string_height(_string); 33 | 34 | var _x = 10; 35 | 36 | draw_set_color(c_black); 37 | draw_set_alpha(_backgroundAlpha); 38 | draw_rectangle(_x, 10, _x + _width + 20, 10 + _height + 20, false); 39 | 40 | draw_text(_x + 10, 21, _string); 41 | draw_set_color(c_white); 42 | draw_set_alpha(1); 43 | draw_text(_x + 10, 20, _string); 44 | 45 | //Info text 46 | var _string = ""; 47 | _string += "This is the actual surface used for color data --->\n"; 48 | _string += "Each column is a palette\n"; 49 | _string += "Each row is a replacement colour\n"; 50 | 51 | var _width = string_width(_string); 52 | var _height = string_height(_string); 53 | 54 | var _x = room_width - 150 - _width; 55 | 56 | draw_set_color(c_black); 57 | draw_set_alpha(_backgroundAlpha); 58 | draw_rectangle(_x, 10, _x + _width + 20, 10 + _height + 20, false); 59 | 60 | draw_text(_x + 10, 21, _string); 61 | draw_set_color(c_white); 62 | draw_set_alpha(1); 63 | draw_text(_x + 10, 20, _string); -------------------------------------------------------------------------------- /options/ios/options_ios.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMiOSOptions":"v1", 3 | "%Name":"iOS", 4 | "name":"iOS", 5 | "option_ios_build_number":0, 6 | "option_ios_bundle_name":"com.company.game", 7 | "option_ios_defer_home_indicator":false, 8 | "option_ios_devices":2, 9 | "option_ios_display_name":"Created with GameMaker", 10 | "option_ios_enable_broadcast":false, 11 | "option_ios_half_ipad1_textures":false, 12 | "option_ios_icon_ipad_app_152":"${base_options_dir}/ios/icons/app/ipad_152.png", 13 | "option_ios_icon_ipad_app_76":"${base_options_dir}/ios/icons/app/ipad_76.png", 14 | "option_ios_icon_ipad_notification_20":"${base_options_dir}/ios/icons/notification/ipad_20.png", 15 | "option_ios_icon_ipad_notification_40":"${base_options_dir}/ios/icons/notification/ipad_40.png", 16 | "option_ios_icon_ipad_pro_app_167":"${base_options_dir}/ios/icons/app/ipad_pro_167.png", 17 | "option_ios_icon_ipad_settings_29":"${base_options_dir}/ios/icons/settings/ipad_29.png", 18 | "option_ios_icon_ipad_settings_58":"${base_options_dir}/ios/icons/settings/ipad_58.png", 19 | "option_ios_icon_ipad_spotlight_40":"${base_options_dir}/ios/icons/spotlight/ipad_40.png", 20 | "option_ios_icon_ipad_spotlight_80":"${base_options_dir}/ios/icons/spotlight/ipad_80.png", 21 | "option_ios_icon_iphone_app_120":"${base_options_dir}/ios/icons/app/iphone_120.png", 22 | "option_ios_icon_iphone_app_180":"${base_options_dir}/ios/icons/app/iphone_180.png", 23 | "option_ios_icon_iphone_notification_40":"${base_options_dir}/ios/icons/notification/iphone_40.png", 24 | "option_ios_icon_iphone_notification_60":"${base_options_dir}/ios/icons/notification/iphone_60.png", 25 | "option_ios_icon_iphone_settings_58":"${base_options_dir}/ios/icons/settings/iphone_58.png", 26 | "option_ios_icon_iphone_settings_87":"${base_options_dir}/ios/icons/settings/iphone_87.png", 27 | "option_ios_icon_iphone_spotlight_120":"${base_options_dir}/ios/icons/spotlight/iphone_120.png", 28 | "option_ios_icon_iphone_spotlight_80":"${base_options_dir}/ios/icons/spotlight/iphone_80.png", 29 | "option_ios_icon_itunes_artwork_1024":"${base_options_dir}/ios/icons/itunes/itunes_1024.png", 30 | "option_ios_interpolate_pixels":false, 31 | "option_ios_launchscreen_fill":0, 32 | "option_ios_launchscreen_image":"${base_options_dir}/ios/splash/launchscreen.png", 33 | "option_ios_launchscreen_image_landscape":"${base_options_dir}/ios/splash/launchscreen-landscape.png", 34 | "option_ios_min_version":"10.0", 35 | "option_ios_orientation_landscape":true, 36 | "option_ios_orientation_landscape_flipped":true, 37 | "option_ios_orientation_portrait":true, 38 | "option_ios_orientation_portrait_flipped":true, 39 | "option_ios_output_dir":"~/gamemakerstudio2", 40 | "option_ios_podfile_lock_path":"${options_dir}/ios/Podfile.lock", 41 | "option_ios_podfile_path":"${options_dir}/ios/Podfile", 42 | "option_ios_scale":0, 43 | "option_ios_splashscreen_background_colour":255, 44 | "option_ios_team_id":"", 45 | "option_ios_texture_page":"2048x2048", 46 | "option_ios_version":"1.0.0.0", 47 | "resourceType":"GMiOSOptions", 48 | "resourceVersion":"2.0", 49 | } -------------------------------------------------------------------------------- /ColorMod.yyp: -------------------------------------------------------------------------------- 1 | { 2 | "$GMProject":"", 3 | "%Name":"ColorMod", 4 | "AudioGroups":[ 5 | {"$GMAudioGroup":"","%Name":"audiogroup_default","name":"audiogroup_default","resourceType":"GMAudioGroup","resourceVersion":"2.0","targets":-1,}, 6 | ], 7 | "configs":{ 8 | "children":[], 9 | "name":"Default", 10 | }, 11 | "defaultScriptType":1, 12 | "Folders":[ 13 | {"$GMFolder":"","%Name":"ColorMod","folderPath":"folders/ColorMod.yy","name":"ColorMod","resourceType":"GMFolder","resourceVersion":"2.0",}, 14 | {"$GMFolder":"","%Name":"(System)","folderPath":"folders/ColorMod/(System).yy","name":"(System)","resourceType":"GMFolder","resourceVersion":"2.0",}, 15 | ], 16 | "IncludedFiles":[], 17 | "isEcma":false, 18 | "LibraryEmitters":[], 19 | "MetaData":{ 20 | "IDEVersion":"2024.8.1.171", 21 | }, 22 | "name":"ColorMod", 23 | "resources":[ 24 | {"id":{"name":"oTestArrays","path":"objects/oTestArrays/oTestArrays.yy",},}, 25 | {"id":{"name":"oTestSprite","path":"objects/oTestSprite/oTestSprite.yy",},}, 26 | {"id":{"name":"rMain","path":"rooms/rMain/rMain.yy",},}, 27 | {"id":{"name":"ColorMod","path":"scripts/ColorMod/ColorMod.yy",},}, 28 | {"id":{"name":"ColorModDestroy","path":"scripts/ColorModDestroy/ColorModDestroy.yy",},}, 29 | {"id":{"name":"ColorModFromSprite","path":"scripts/ColorModFromSprite/ColorModFromSprite.yy",},}, 30 | {"id":{"name":"ColorModSpriteColumnToArray","path":"scripts/ColorModSpriteColumnToArray/ColorModSpriteColumnToArray.yy",},}, 31 | {"id":{"name":"ColorModSpriteRowToArray","path":"scripts/ColorModSpriteRowToArray/ColorModSpriteRowToArray.yy",},}, 32 | {"id":{"name":"__shdColorMod","path":"shaders/__shdColorMod/__shdColorMod.yy",},}, 33 | {"id":{"name":"__shdColorModBlend","path":"shaders/__shdColorModBlend/__shdColorModBlend.yy",},}, 34 | {"id":{"name":"__shdColorModBlendDebug","path":"shaders/__shdColorModBlendDebug/__shdColorModBlendDebug.yy",},}, 35 | {"id":{"name":"__shdColorModDebug","path":"shaders/__shdColorModDebug/__shdColorModDebug.yy",},}, 36 | {"id":{"name":"__shdColorModDebugOutput","path":"shaders/__shdColorModDebugOutput/__shdColorModDebugOutput.yy",},}, 37 | {"id":{"name":"__sColorModPixel","path":"sprites/__sColorModPixel/__sColorModPixel.yy",},}, 38 | {"id":{"name":"sTestBase","path":"sprites/sTestBase/sTestBase.yy",},}, 39 | {"id":{"name":"sTestHair","path":"sprites/sTestHair/sTestHair.yy",},}, 40 | {"id":{"name":"sTestPaletteColumns","path":"sprites/sTestPaletteColumns/sTestPaletteColumns.yy",},}, 41 | {"id":{"name":"sTestPaletteRows","path":"sprites/sTestPaletteRows/sTestPaletteRows.yy",},}, 42 | ], 43 | "resourceType":"GMProject", 44 | "resourceVersion":"2.0", 45 | "RoomOrderNodes":[ 46 | {"roomId":{"name":"rMain","path":"rooms/rMain/rMain.yy",},}, 47 | ], 48 | "templateType":null, 49 | "TextureGroups":[ 50 | {"$GMTextureGroup":"","%Name":"Default","autocrop":true,"border":2,"compressFormat":"bz2","customOptions":"","directory":"","groupParent":null,"isScaled":true,"loadType":"default","mipsToGenerate":0,"name":"Default","resourceType":"GMTextureGroup","resourceVersion":"2.0","targets":-1,}, 51 | ], 52 | } -------------------------------------------------------------------------------- /sprites/sTestBase/sTestBase.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMSprite":"", 3 | "%Name":"sTestBase", 4 | "bboxMode":0, 5 | "bbox_bottom":64, 6 | "bbox_left":26, 7 | "bbox_right":53, 8 | "bbox_top":22, 9 | "collisionKind":1, 10 | "collisionTolerance":0, 11 | "DynamicTexturePage":false, 12 | "edgeFiltering":false, 13 | "For3D":false, 14 | "frames":[ 15 | {"$GMSpriteFrame":"","%Name":"ea61c840-8a5d-429f-8833-009abe8a414a","name":"ea61c840-8a5d-429f-8833-009abe8a414a","resourceType":"GMSpriteFrame","resourceVersion":"2.0",}, 16 | ], 17 | "gridX":0, 18 | "gridY":0, 19 | "height":80, 20 | "HTile":false, 21 | "layers":[ 22 | {"$GMImageLayer":"","%Name":"099b1ece-a9c4-4d56-9537-eaeacf89e0d1","blendMode":0,"displayName":"default","isLocked":false,"name":"099b1ece-a9c4-4d56-9537-eaeacf89e0d1","opacity":100.0,"resourceType":"GMImageLayer","resourceVersion":"2.0","visible":true,}, 23 | ], 24 | "name":"sTestBase", 25 | "nineSlice":null, 26 | "origin":4, 27 | "parent":{ 28 | "name":"ColorMod", 29 | "path":"ColorMod.yyp", 30 | }, 31 | "preMultiplyAlpha":false, 32 | "resourceType":"GMSprite", 33 | "resourceVersion":"2.0", 34 | "sequence":{ 35 | "$GMSequence":"", 36 | "%Name":"sTestBase", 37 | "autoRecord":true, 38 | "backdropHeight":768, 39 | "backdropImageOpacity":0.5, 40 | "backdropImagePath":"", 41 | "backdropWidth":1366, 42 | "backdropXOffset":0.0, 43 | "backdropYOffset":0.0, 44 | "events":{ 45 | "$KeyframeStore":"", 46 | "Keyframes":[], 47 | "resourceType":"KeyframeStore", 48 | "resourceVersion":"2.0", 49 | }, 50 | "eventStubScript":null, 51 | "eventToFunction":{}, 52 | "length":1.0, 53 | "lockOrigin":false, 54 | "moments":{ 55 | "$KeyframeStore":"", 56 | "Keyframes":[], 57 | "resourceType":"KeyframeStore", 58 | "resourceVersion":"2.0", 59 | }, 60 | "name":"sTestBase", 61 | "playback":1, 62 | "playbackSpeed":30.0, 63 | "playbackSpeedType":0, 64 | "resourceType":"GMSequence", 65 | "resourceVersion":"2.0", 66 | "showBackdrop":true, 67 | "showBackdropImage":false, 68 | "timeUnits":1, 69 | "tracks":[ 70 | {"$GMSpriteFramesTrack":"","builtinName":0,"events":[],"inheritsTrackColour":true,"interpolation":1,"isCreationTrack":false,"keyframes":{"$KeyframeStore":"","Keyframes":[ 71 | {"$Keyframe":"","Channels":{ 72 | "0":{"$SpriteFrameKeyframe":"","Id":{"name":"ea61c840-8a5d-429f-8833-009abe8a414a","path":"sprites/sTestBase/sTestBase.yy",},"resourceType":"SpriteFrameKeyframe","resourceVersion":"2.0",}, 73 | },"Disabled":false,"id":"c095f91b-1cd1-46ce-85dc-2f27a01ecf36","IsCreationKey":false,"Key":0.0,"Length":1.0,"resourceType":"Keyframe","resourceVersion":"2.0","Stretch":false,}, 74 | ],"resourceType":"KeyframeStore","resourceVersion":"2.0",},"modifiers":[],"name":"frames","resourceType":"GMSpriteFramesTrack","resourceVersion":"2.0","spriteId":null,"trackColour":0,"tracks":[],"traits":0,}, 75 | ], 76 | "visibleRange":null, 77 | "volume":1.0, 78 | "xorigin":40, 79 | "yorigin":40, 80 | }, 81 | "swatchColours":null, 82 | "swfPrecision":2.525, 83 | "textureGroupId":{ 84 | "name":"Default", 85 | "path":"texturegroups/Default", 86 | }, 87 | "type":0, 88 | "VTile":false, 89 | "width":80, 90 | } -------------------------------------------------------------------------------- /sprites/sTestHair/sTestHair.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMSprite":"", 3 | "%Name":"sTestHair", 4 | "bboxMode":0, 5 | "bbox_bottom":36, 6 | "bbox_left":32, 7 | "bbox_right":47, 8 | "bbox_top":22, 9 | "collisionKind":1, 10 | "collisionTolerance":0, 11 | "DynamicTexturePage":false, 12 | "edgeFiltering":false, 13 | "For3D":false, 14 | "frames":[ 15 | {"$GMSpriteFrame":"","%Name":"ea61c840-8a5d-429f-8833-009abe8a414a","name":"ea61c840-8a5d-429f-8833-009abe8a414a","resourceType":"GMSpriteFrame","resourceVersion":"2.0",}, 16 | ], 17 | "gridX":0, 18 | "gridY":0, 19 | "height":80, 20 | "HTile":false, 21 | "layers":[ 22 | {"$GMImageLayer":"","%Name":"099b1ece-a9c4-4d56-9537-eaeacf89e0d1","blendMode":0,"displayName":"default","isLocked":false,"name":"099b1ece-a9c4-4d56-9537-eaeacf89e0d1","opacity":100.0,"resourceType":"GMImageLayer","resourceVersion":"2.0","visible":true,}, 23 | ], 24 | "name":"sTestHair", 25 | "nineSlice":null, 26 | "origin":4, 27 | "parent":{ 28 | "name":"ColorMod", 29 | "path":"ColorMod.yyp", 30 | }, 31 | "preMultiplyAlpha":false, 32 | "resourceType":"GMSprite", 33 | "resourceVersion":"2.0", 34 | "sequence":{ 35 | "$GMSequence":"", 36 | "%Name":"sTestHair", 37 | "autoRecord":true, 38 | "backdropHeight":768, 39 | "backdropImageOpacity":0.5, 40 | "backdropImagePath":"", 41 | "backdropWidth":1366, 42 | "backdropXOffset":0.0, 43 | "backdropYOffset":0.0, 44 | "events":{ 45 | "$KeyframeStore":"", 46 | "Keyframes":[], 47 | "resourceType":"KeyframeStore", 48 | "resourceVersion":"2.0", 49 | }, 50 | "eventStubScript":null, 51 | "eventToFunction":{}, 52 | "length":1.0, 53 | "lockOrigin":false, 54 | "moments":{ 55 | "$KeyframeStore":"", 56 | "Keyframes":[], 57 | "resourceType":"KeyframeStore", 58 | "resourceVersion":"2.0", 59 | }, 60 | "name":"sTestHair", 61 | "playback":1, 62 | "playbackSpeed":30.0, 63 | "playbackSpeedType":0, 64 | "resourceType":"GMSequence", 65 | "resourceVersion":"2.0", 66 | "showBackdrop":true, 67 | "showBackdropImage":false, 68 | "timeUnits":1, 69 | "tracks":[ 70 | {"$GMSpriteFramesTrack":"","builtinName":0,"events":[],"inheritsTrackColour":true,"interpolation":1,"isCreationTrack":false,"keyframes":{"$KeyframeStore":"","Keyframes":[ 71 | {"$Keyframe":"","Channels":{ 72 | "0":{"$SpriteFrameKeyframe":"","Id":{"name":"ea61c840-8a5d-429f-8833-009abe8a414a","path":"sprites/sTestHair/sTestHair.yy",},"resourceType":"SpriteFrameKeyframe","resourceVersion":"2.0",}, 73 | },"Disabled":false,"id":"e2e409d7-63e6-4fd4-9748-c19139ea082b","IsCreationKey":false,"Key":0.0,"Length":1.0,"resourceType":"Keyframe","resourceVersion":"2.0","Stretch":false,}, 74 | ],"resourceType":"KeyframeStore","resourceVersion":"2.0",},"modifiers":[],"name":"frames","resourceType":"GMSpriteFramesTrack","resourceVersion":"2.0","spriteId":null,"trackColour":0,"tracks":[],"traits":0,}, 75 | ], 76 | "visibleRange":null, 77 | "volume":1.0, 78 | "xorigin":40, 79 | "yorigin":40, 80 | }, 81 | "swatchColours":null, 82 | "swfPrecision":2.525, 83 | "textureGroupId":{ 84 | "name":"Default", 85 | "path":"texturegroups/Default", 86 | }, 87 | "type":0, 88 | "VTile":false, 89 | "width":80, 90 | } -------------------------------------------------------------------------------- /sprites/sTestPaletteRows/sTestPaletteRows.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMSprite":"", 3 | "%Name":"sTestPaletteRows", 4 | "bboxMode":0, 5 | "bbox_bottom":3, 6 | "bbox_left":0, 7 | "bbox_right":20, 8 | "bbox_top":0, 9 | "collisionKind":1, 10 | "collisionTolerance":0, 11 | "DynamicTexturePage":false, 12 | "edgeFiltering":false, 13 | "For3D":false, 14 | "frames":[ 15 | {"$GMSpriteFrame":"","%Name":"3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c","name":"3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c","resourceType":"GMSpriteFrame","resourceVersion":"2.0",}, 16 | ], 17 | "gridX":0, 18 | "gridY":0, 19 | "height":4, 20 | "HTile":false, 21 | "layers":[ 22 | {"$GMImageLayer":"","%Name":"c8bb4b3f-e4ce-4289-8e2e-cbb36fa8a432","blendMode":0,"displayName":"default","isLocked":false,"name":"c8bb4b3f-e4ce-4289-8e2e-cbb36fa8a432","opacity":100.0,"resourceType":"GMImageLayer","resourceVersion":"2.0","visible":true,}, 23 | ], 24 | "name":"sTestPaletteRows", 25 | "nineSlice":null, 26 | "origin":0, 27 | "parent":{ 28 | "name":"ColorMod", 29 | "path":"ColorMod.yyp", 30 | }, 31 | "preMultiplyAlpha":false, 32 | "resourceType":"GMSprite", 33 | "resourceVersion":"2.0", 34 | "sequence":{ 35 | "$GMSequence":"", 36 | "%Name":"sTestPaletteRows", 37 | "autoRecord":true, 38 | "backdropHeight":768, 39 | "backdropImageOpacity":0.5, 40 | "backdropImagePath":"", 41 | "backdropWidth":1366, 42 | "backdropXOffset":0.0, 43 | "backdropYOffset":0.0, 44 | "events":{ 45 | "$KeyframeStore":"", 46 | "Keyframes":[], 47 | "resourceType":"KeyframeStore", 48 | "resourceVersion":"2.0", 49 | }, 50 | "eventStubScript":null, 51 | "eventToFunction":{}, 52 | "length":1.0, 53 | "lockOrigin":false, 54 | "moments":{ 55 | "$KeyframeStore":"", 56 | "Keyframes":[], 57 | "resourceType":"KeyframeStore", 58 | "resourceVersion":"2.0", 59 | }, 60 | "name":"sTestPaletteRows", 61 | "playback":1, 62 | "playbackSpeed":30.0, 63 | "playbackSpeedType":0, 64 | "resourceType":"GMSequence", 65 | "resourceVersion":"2.0", 66 | "showBackdrop":true, 67 | "showBackdropImage":false, 68 | "timeUnits":1, 69 | "tracks":[ 70 | {"$GMSpriteFramesTrack":"","builtinName":0,"events":[],"inheritsTrackColour":true,"interpolation":1,"isCreationTrack":false,"keyframes":{"$KeyframeStore":"","Keyframes":[ 71 | {"$Keyframe":"","Channels":{ 72 | "0":{"$SpriteFrameKeyframe":"","Id":{"name":"3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c","path":"sprites/sTestPaletteRows/sTestPaletteRows.yy",},"resourceType":"SpriteFrameKeyframe","resourceVersion":"2.0",}, 73 | },"Disabled":false,"id":"ebced8cd-ec22-439c-b91f-7e8648c1048e","IsCreationKey":false,"Key":0.0,"Length":1.0,"resourceType":"Keyframe","resourceVersion":"2.0","Stretch":false,}, 74 | ],"resourceType":"KeyframeStore","resourceVersion":"2.0",},"modifiers":[],"name":"frames","resourceType":"GMSpriteFramesTrack","resourceVersion":"2.0","spriteId":null,"trackColour":0,"tracks":[],"traits":0,}, 75 | ], 76 | "visibleRange":null, 77 | "volume":1.0, 78 | "xorigin":0, 79 | "yorigin":0, 80 | }, 81 | "swatchColours":null, 82 | "swfPrecision":0.5, 83 | "textureGroupId":{ 84 | "name":"Default", 85 | "path":"texturegroups/Default", 86 | }, 87 | "type":0, 88 | "VTile":false, 89 | "width":21, 90 | } -------------------------------------------------------------------------------- /sprites/__sColorModPixel/__sColorModPixel.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMSprite":"", 3 | "%Name":"__sColorModPixel", 4 | "bboxMode":0, 5 | "bbox_bottom":0, 6 | "bbox_left":0, 7 | "bbox_right":0, 8 | "bbox_top":0, 9 | "collisionKind":1, 10 | "collisionTolerance":0, 11 | "DynamicTexturePage":false, 12 | "edgeFiltering":false, 13 | "For3D":false, 14 | "frames":[ 15 | {"$GMSpriteFrame":"","%Name":"fabd34c8-9f33-4031-8a35-e19a5fef1f46","name":"fabd34c8-9f33-4031-8a35-e19a5fef1f46","resourceType":"GMSpriteFrame","resourceVersion":"2.0",}, 16 | ], 17 | "gridX":0, 18 | "gridY":0, 19 | "height":1, 20 | "HTile":false, 21 | "layers":[ 22 | {"$GMImageLayer":"","%Name":"2f675806-ca23-432f-bb71-42ee681f682e","blendMode":0,"displayName":"default","isLocked":false,"name":"2f675806-ca23-432f-bb71-42ee681f682e","opacity":100.0,"resourceType":"GMImageLayer","resourceVersion":"2.0","visible":true,}, 23 | ], 24 | "name":"__sColorModPixel", 25 | "nineSlice":null, 26 | "origin":0, 27 | "parent":{ 28 | "name":"(System)", 29 | "path":"folders/ColorMod/(System).yy", 30 | }, 31 | "preMultiplyAlpha":false, 32 | "resourceType":"GMSprite", 33 | "resourceVersion":"2.0", 34 | "sequence":{ 35 | "$GMSequence":"", 36 | "%Name":"__sColorModPixel", 37 | "autoRecord":true, 38 | "backdropHeight":768, 39 | "backdropImageOpacity":0.5, 40 | "backdropImagePath":"", 41 | "backdropWidth":1366, 42 | "backdropXOffset":0.0, 43 | "backdropYOffset":0.0, 44 | "events":{ 45 | "$KeyframeStore":"", 46 | "Keyframes":[], 47 | "resourceType":"KeyframeStore", 48 | "resourceVersion":"2.0", 49 | }, 50 | "eventStubScript":null, 51 | "eventToFunction":{}, 52 | "length":1.0, 53 | "lockOrigin":false, 54 | "moments":{ 55 | "$KeyframeStore":"", 56 | "Keyframes":[], 57 | "resourceType":"KeyframeStore", 58 | "resourceVersion":"2.0", 59 | }, 60 | "name":"__sColorModPixel", 61 | "playback":1, 62 | "playbackSpeed":30.0, 63 | "playbackSpeedType":0, 64 | "resourceType":"GMSequence", 65 | "resourceVersion":"2.0", 66 | "showBackdrop":true, 67 | "showBackdropImage":false, 68 | "timeUnits":1, 69 | "tracks":[ 70 | {"$GMSpriteFramesTrack":"","builtinName":0,"events":[],"inheritsTrackColour":true,"interpolation":1,"isCreationTrack":false,"keyframes":{"$KeyframeStore":"","Keyframes":[ 71 | {"$Keyframe":"","Channels":{ 72 | "0":{"$SpriteFrameKeyframe":"","Id":{"name":"fabd34c8-9f33-4031-8a35-e19a5fef1f46","path":"sprites/__sColorModPixel/__sColorModPixel.yy",},"resourceType":"SpriteFrameKeyframe","resourceVersion":"2.0",}, 73 | },"Disabled":false,"id":"a9ccd45e-a323-4d85-bbe9-854f2689dd94","IsCreationKey":false,"Key":0.0,"Length":1.0,"resourceType":"Keyframe","resourceVersion":"2.0","Stretch":false,}, 74 | ],"resourceType":"KeyframeStore","resourceVersion":"2.0",},"modifiers":[],"name":"frames","resourceType":"GMSpriteFramesTrack","resourceVersion":"2.0","spriteId":null,"trackColour":0,"tracks":[],"traits":0,}, 75 | ], 76 | "visibleRange":null, 77 | "volume":1.0, 78 | "xorigin":0, 79 | "yorigin":0, 80 | }, 81 | "swatchColours":null, 82 | "swfPrecision":2.525, 83 | "textureGroupId":{ 84 | "name":"Default", 85 | "path":"texturegroups/Default", 86 | }, 87 | "type":0, 88 | "VTile":false, 89 | "width":1, 90 | } -------------------------------------------------------------------------------- /sprites/sTestPaletteColumns/sTestPaletteColumns.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMSprite":"", 3 | "%Name":"sTestPaletteColumns", 4 | "bboxMode":0, 5 | "bbox_bottom":20, 6 | "bbox_left":0, 7 | "bbox_right":3, 8 | "bbox_top":0, 9 | "collisionKind":1, 10 | "collisionTolerance":0, 11 | "DynamicTexturePage":false, 12 | "edgeFiltering":false, 13 | "For3D":false, 14 | "frames":[ 15 | {"$GMSpriteFrame":"","%Name":"3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c","name":"3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c","resourceType":"GMSpriteFrame","resourceVersion":"2.0",}, 16 | ], 17 | "gridX":0, 18 | "gridY":0, 19 | "height":21, 20 | "HTile":false, 21 | "layers":[ 22 | {"$GMImageLayer":"","%Name":"c8bb4b3f-e4ce-4289-8e2e-cbb36fa8a432","blendMode":0,"displayName":"default","isLocked":false,"name":"c8bb4b3f-e4ce-4289-8e2e-cbb36fa8a432","opacity":100.0,"resourceType":"GMImageLayer","resourceVersion":"2.0","visible":true,}, 23 | ], 24 | "name":"sTestPaletteColumns", 25 | "nineSlice":null, 26 | "origin":0, 27 | "parent":{ 28 | "name":"ColorMod", 29 | "path":"ColorMod.yyp", 30 | }, 31 | "preMultiplyAlpha":false, 32 | "resourceType":"GMSprite", 33 | "resourceVersion":"2.0", 34 | "sequence":{ 35 | "$GMSequence":"", 36 | "%Name":"sTestPaletteColumns", 37 | "autoRecord":true, 38 | "backdropHeight":768, 39 | "backdropImageOpacity":0.5, 40 | "backdropImagePath":"", 41 | "backdropWidth":1366, 42 | "backdropXOffset":0.0, 43 | "backdropYOffset":0.0, 44 | "events":{ 45 | "$KeyframeStore":"", 46 | "Keyframes":[], 47 | "resourceType":"KeyframeStore", 48 | "resourceVersion":"2.0", 49 | }, 50 | "eventStubScript":null, 51 | "eventToFunction":{}, 52 | "length":1.0, 53 | "lockOrigin":false, 54 | "moments":{ 55 | "$KeyframeStore":"", 56 | "Keyframes":[], 57 | "resourceType":"KeyframeStore", 58 | "resourceVersion":"2.0", 59 | }, 60 | "name":"sTestPaletteColumns", 61 | "playback":1, 62 | "playbackSpeed":30.0, 63 | "playbackSpeedType":0, 64 | "resourceType":"GMSequence", 65 | "resourceVersion":"2.0", 66 | "showBackdrop":true, 67 | "showBackdropImage":false, 68 | "timeUnits":1, 69 | "tracks":[ 70 | {"$GMSpriteFramesTrack":"","builtinName":0,"events":[],"inheritsTrackColour":true,"interpolation":1,"isCreationTrack":false,"keyframes":{"$KeyframeStore":"","Keyframes":[ 71 | {"$Keyframe":"","Channels":{ 72 | "0":{"$SpriteFrameKeyframe":"","Id":{"name":"3c73c2bf-f6c5-4901-8a21-9b6a2c2aed6c","path":"sprites/sTestPaletteColumns/sTestPaletteColumns.yy",},"resourceType":"SpriteFrameKeyframe","resourceVersion":"2.0",}, 73 | },"Disabled":false,"id":"ebced8cd-ec22-439c-b91f-7e8648c1048e","IsCreationKey":false,"Key":0.0,"Length":1.0,"resourceType":"Keyframe","resourceVersion":"2.0","Stretch":false,}, 74 | ],"resourceType":"KeyframeStore","resourceVersion":"2.0",},"modifiers":[],"name":"frames","resourceType":"GMSpriteFramesTrack","resourceVersion":"2.0","spriteId":null,"trackColour":0,"tracks":[],"traits":0,}, 75 | ], 76 | "visibleRange":null, 77 | "volume":1.0, 78 | "xorigin":0, 79 | "yorigin":0, 80 | }, 81 | "swatchColours":null, 82 | "swfPrecision":0.5, 83 | "textureGroupId":{ 84 | "name":"Default", 85 | "path":"texturegroups/Default", 86 | }, 87 | "type":0, 88 | "VTile":false, 89 | "width":4, 90 | } -------------------------------------------------------------------------------- /scripts/ColorModFromSprite/ColorModFromSprite.gml: -------------------------------------------------------------------------------- 1 | // Feather disable all 2 | 3 | /// `ColorModFromSprite(sprite, image, useRows, [debugMode=false])` 4 | /// 5 | /// Convenience function to create a ColorMod struct from a source sprite. If the `useRows` 6 | /// parameter is set to `true` then the sprite will be processed row by row, if set to `false` 7 | /// the sprite will be processed column by column. The default / unadjusted palette will be the 8 | /// top-most row or left-most column. 9 | /// 10 | /// The "name" of each palette within the ColorMod struct will be the y position of the row in the 11 | /// sprite (or the x position of the column). The default / unadjusted palette is always index 0. 12 | /// 13 | /// 14 | /// 15 | /// Please see the documentation in the `ColorMod` script for a full list of methods available on 16 | /// a ColorMod struct. Basic use follows: 17 | /// 18 | /// Step 1: Create a ColorMod struct at the start of the game 19 | /// global.colorModForPlayer = ColorModFromSprite(sPlayerPalette, 0, true); 20 | /// 21 | /// Step 2: In a Draw event, set the ColorMod shader, draw a sprite, then reset the shader 22 | /// global.colorModForPlayer.SetShader(paletteIndex); 23 | /// draw_sprite(sPlayer, 0, x, y); 24 | /// shader_reset(); 25 | /// 26 | /// 27 | /// 28 | /// If the `debugMode` parameter is set to `true` then any colors in the drawn image that are not 29 | /// in the default palette will usually be highlighted in bright fuchsia. Drawing with debug mode 30 | /// turned on will significantly decrease performance so remember to turn it off before compiling 31 | /// your game for other people to play. 32 | /// 33 | /// The `moduloHint` parameter allows you to provide a pre-calculated modulo value. This skips the 34 | /// slow modulo calculation step when first creating the ColorMod struct. You should calculate a 35 | /// modulo value by running the game then copying that modulo value into your codebase for 36 | /// subsequent runs of the game. You will need to update the modulo hint if your default palette 37 | /// changes (but not alternate palettes). 38 | /// 39 | /// N.B. This function is fairly slow and you should generally only call this function once when 40 | /// the game boots. 41 | /// 42 | /// N.B. ColorMod is not compatible with antialiased art or art drawn with texture filtering / 43 | /// bilinear interpolation switched on. ColorMod should only be used with pixel art. 44 | /// 45 | /// 46 | /// 47 | /// @param sprite 48 | /// @param image 49 | /// @param useRows 50 | /// @param [debugMode=false] 51 | /// @param [moduloHint] 52 | 53 | function ColorModFromSprite(_sprite, _image, _useRows, _debugMode = false, _moduloHint = undefined) 54 | { 55 | var _width = sprite_get_width(_sprite); 56 | var _height = sprite_get_height(_sprite); 57 | 58 | if (_useRows) 59 | { 60 | var _sourceArray = ColorModSpriteRowToArray(_sprite, _image, 0); 61 | var _colorMod = new ColorMod(_sourceArray, _height, _debugMode, _moduloHint); 62 | 63 | var _y = 0; 64 | repeat(_height) 65 | { 66 | _colorMod.PaletteAdd(_y, ColorModSpriteRowToArray(_sprite, _image, _y)); 67 | ++_y; 68 | } 69 | } 70 | else 71 | { 72 | var _sourceArray = ColorModSpriteColumnToArray(_sprite, _image, 0); 73 | var _colorMod = new ColorMod(_sourceArray, _width, _debugMode, _moduloHint); 74 | 75 | var _x = 0; 76 | repeat(_width) 77 | { 78 | _colorMod.PaletteAdd(_x, ColorModSpriteColumnToArray(_sprite, _image, _x)); 79 | ++_x; 80 | } 81 | } 82 | 83 | _colorMod.EnsureSurface(); 84 | 85 | return _colorMod; 86 | } -------------------------------------------------------------------------------- /rooms/rMain/rMain.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMRoom":"v1", 3 | "%Name":"rMain", 4 | "creationCodeFile":"", 5 | "inheritCode":false, 6 | "inheritCreationOrder":false, 7 | "inheritLayers":false, 8 | "instanceCreationOrder":[ 9 | {"name":"inst_2E3CB5F2","path":"rooms/rMain/rMain.yy",}, 10 | ], 11 | "isDnd":false, 12 | "layers":[ 13 | {"$GMRInstanceLayer":"","%Name":"Instances","depth":0,"effectEnabled":true,"effectType":null,"gridX":32,"gridY":32,"hierarchyFrozen":false,"inheritLayerDepth":false,"inheritLayerSettings":false,"inheritSubLayers":true,"inheritVisibility":true,"instances":[ 14 | {"$GMRInstance":"v1","%Name":"inst_2E3CB5F2","colour":4294967295,"frozen":false,"hasCreationCode":false,"ignore":false,"imageIndex":0,"imageSpeed":1.0,"inheritCode":false,"inheritedItemId":null,"inheritItemSettings":false,"isDnd":false,"name":"inst_2E3CB5F2","objectId":{"name":"oTestSprite","path":"objects/oTestSprite/oTestSprite.yy",},"properties":[],"resourceType":"GMRInstance","resourceVersion":"2.0","rotation":0.0,"scaleX":1.0,"scaleY":1.0,"x":32.0,"y":32.0,}, 15 | ],"layers":[],"name":"Instances","properties":[],"resourceType":"GMRInstanceLayer","resourceVersion":"2.0","userdefinedDepth":false,"visible":true,}, 16 | {"$GMRBackgroundLayer":"","%Name":"Background","animationFPS":15.0,"animationSpeedType":0,"colour":4284900966,"depth":100,"effectEnabled":true,"effectType":null,"gridX":32,"gridY":32,"hierarchyFrozen":false,"hspeed":0.0,"htiled":false,"inheritLayerDepth":false,"inheritLayerSettings":false,"inheritSubLayers":true,"inheritVisibility":true,"layers":[],"name":"Background","properties":[],"resourceType":"GMRBackgroundLayer","resourceVersion":"2.0","spriteId":null,"stretch":false,"userdefinedAnimFPS":false,"userdefinedDepth":false,"visible":true,"vspeed":0.0,"vtiled":false,"x":0,"y":0,}, 17 | ], 18 | "name":"rMain", 19 | "parent":{ 20 | "name":"ColorMod", 21 | "path":"ColorMod.yyp", 22 | }, 23 | "parentRoom":null, 24 | "physicsSettings":{ 25 | "inheritPhysicsSettings":false, 26 | "PhysicsWorld":false, 27 | "PhysicsWorldGravityX":0.0, 28 | "PhysicsWorldGravityY":10.0, 29 | "PhysicsWorldPixToMetres":0.1, 30 | }, 31 | "resourceType":"GMRoom", 32 | "resourceVersion":"2.0", 33 | "roomSettings":{ 34 | "Height":768, 35 | "inheritRoomSettings":false, 36 | "persistent":false, 37 | "Width":1366, 38 | }, 39 | "sequenceId":null, 40 | "views":[ 41 | {"hborder":32,"hport":768,"hspeed":-1,"hview":768,"inherit":false,"objectId":null,"vborder":32,"visible":false,"vspeed":-1,"wport":1366,"wview":1366,"xport":0,"xview":0,"yport":0,"yview":0,}, 42 | {"hborder":32,"hport":768,"hspeed":-1,"hview":768,"inherit":false,"objectId":null,"vborder":32,"visible":false,"vspeed":-1,"wport":1366,"wview":1366,"xport":0,"xview":0,"yport":0,"yview":0,}, 43 | {"hborder":32,"hport":768,"hspeed":-1,"hview":768,"inherit":false,"objectId":null,"vborder":32,"visible":false,"vspeed":-1,"wport":1366,"wview":1366,"xport":0,"xview":0,"yport":0,"yview":0,}, 44 | {"hborder":32,"hport":768,"hspeed":-1,"hview":768,"inherit":false,"objectId":null,"vborder":32,"visible":false,"vspeed":-1,"wport":1366,"wview":1366,"xport":0,"xview":0,"yport":0,"yview":0,}, 45 | {"hborder":32,"hport":768,"hspeed":-1,"hview":768,"inherit":false,"objectId":null,"vborder":32,"visible":false,"vspeed":-1,"wport":1366,"wview":1366,"xport":0,"xview":0,"yport":0,"yview":0,}, 46 | {"hborder":32,"hport":768,"hspeed":-1,"hview":768,"inherit":false,"objectId":null,"vborder":32,"visible":false,"vspeed":-1,"wport":1366,"wview":1366,"xport":0,"xview":0,"yport":0,"yview":0,}, 47 | {"hborder":32,"hport":768,"hspeed":-1,"hview":768,"inherit":false,"objectId":null,"vborder":32,"visible":false,"vspeed":-1,"wport":1366,"wview":1366,"xport":0,"xview":0,"yport":0,"yview":0,}, 48 | {"hborder":32,"hport":768,"hspeed":-1,"hview":768,"inherit":false,"objectId":null,"vborder":32,"visible":false,"vspeed":-1,"wport":1366,"wview":1366,"xport":0,"xview":0,"yport":0,"yview":0,}, 49 | ], 50 | "viewSettings":{ 51 | "clearDisplayBuffer":true, 52 | "clearViewBackground":false, 53 | "enableViews":false, 54 | "inheritViewSettings":false, 55 | }, 56 | "volume":1.0, 57 | } -------------------------------------------------------------------------------- /options/android/options_android.yy: -------------------------------------------------------------------------------- 1 | { 2 | "$GMAndroidOptions":"", 3 | "%Name":"Android", 4 | "name":"Android", 5 | "option_android_application_tag_inject":"", 6 | "option_android_arch_arm64":true, 7 | "option_android_arch_armv7":false, 8 | "option_android_arch_x86_64":false, 9 | "option_android_attribute_allow_backup":false, 10 | "option_android_build_tools":"", 11 | "option_android_compile_sdk":"35", 12 | "option_android_device_support":0, 13 | "option_android_display_name":"Created with GameMaker", 14 | "option_android_facebook_app_display_name":"", 15 | "option_android_facebook_id":"", 16 | "option_android_gamepad_support":true, 17 | "option_android_google_apk_expansion":false, 18 | "option_android_google_cloud_saving":false, 19 | "option_android_google_dynamic_asset_delivery":false, 20 | "option_android_google_licensing_public_key":"", 21 | "option_android_google_services_app_id":"", 22 | "option_android_icon_adaptivebg_hdpi":"${base_options_dir}/android/icons_adaptivebg/hdpi.png", 23 | "option_android_icon_adaptivebg_ldpi":"${base_options_dir}/android/icons_adaptivebg/ldpi.png", 24 | "option_android_icon_adaptivebg_mdpi":"${base_options_dir}/android/icons_adaptivebg/mdpi.png", 25 | "option_android_icon_adaptivebg_xhdpi":"${base_options_dir}/android/icons_adaptivebg/xhdpi.png", 26 | "option_android_icon_adaptivebg_xxhdpi":"${base_options_dir}/android/icons_adaptivebg/xxhdpi.png", 27 | "option_android_icon_adaptivebg_xxxhdpi":"${base_options_dir}/android/icons_adaptivebg/xxxhdpi.png", 28 | "option_android_icon_adaptive_generate":false, 29 | "option_android_icon_adaptive_hdpi":"${base_options_dir}/android/icons_adaptive/hdpi.png", 30 | "option_android_icon_adaptive_ldpi":"${base_options_dir}/android/icons_adaptive/ldpi.png", 31 | "option_android_icon_adaptive_mdpi":"${base_options_dir}/android/icons_adaptive/mdpi.png", 32 | "option_android_icon_adaptive_xhdpi":"${base_options_dir}/android/icons_adaptive/xhdpi.png", 33 | "option_android_icon_adaptive_xxhdpi":"${base_options_dir}/android/icons_adaptive/xxhdpi.png", 34 | "option_android_icon_adaptive_xxxhdpi":"${base_options_dir}/android/icons_adaptive/xxxhdpi.png", 35 | "option_android_icon_hdpi":"${base_options_dir}/android/icons/hdpi.png", 36 | "option_android_icon_ldpi":"${base_options_dir}/android/icons/ldpi.png", 37 | "option_android_icon_mdpi":"${base_options_dir}/android/icons/mdpi.png", 38 | "option_android_icon_xhdpi":"${base_options_dir}/android/icons/xhdpi.png", 39 | "option_android_icon_xxhdpi":"${base_options_dir}/android/icons/xxhdpi.png", 40 | "option_android_icon_xxxhdpi":"${base_options_dir}/android/icons/xxxhdpi.png", 41 | "option_android_install_location":0, 42 | "option_android_interpolate_pixels":false, 43 | "option_android_launchscreen_fill":0, 44 | "option_android_lint":false, 45 | "option_android_logcat":"yoyo:V DEBUG:V AndroidRuntime:V", 46 | "option_android_minimum_sdk":"21", 47 | "option_android_orient_landscape":true, 48 | "option_android_orient_landscape_flipped":true, 49 | "option_android_orient_portrait":true, 50 | "option_android_orient_portrait_flipped":true, 51 | "option_android_package_company":"company", 52 | "option_android_package_domain":"com", 53 | "option_android_package_product":"game", 54 | "option_android_permission_bluetooth":true, 55 | "option_android_permission_internet":true, 56 | "option_android_permission_network_state":false, 57 | "option_android_permission_read_phone_state":false, 58 | "option_android_permission_record_audio":false, 59 | "option_android_permission_write_external_storage":false, 60 | "option_android_proguard_minifying":false, 61 | "option_android_proguard_shrinking":false, 62 | "option_android_scale":0, 63 | "option_android_screen_depth":0, 64 | "option_android_sleep_margin":4, 65 | "option_android_splashscreen_background_colour":255, 66 | "option_android_splash_screens_landscape":"${base_options_dir}/android/splash/landscape.png", 67 | "option_android_splash_screens_portrait":"${base_options_dir}/android/splash/portrait.png", 68 | "option_android_splash_time":0, 69 | "option_android_support_lib":"35.0.0", 70 | "option_android_sync_amazon":false, 71 | "option_android_target_sdk":"35", 72 | "option_android_texture_page":"2048x2048", 73 | "option_android_tools_from_version":false, 74 | "option_android_tv_banner":"${base_options_dir}/android/tv_banner.png", 75 | "option_android_tv_isgame":true, 76 | "option_android_tv_supports_leanback":true, 77 | "option_android_use_facebook":false, 78 | "option_android_version":"1.0.0.0", 79 | "resourceType":"GMAndroidOptions", 80 | "resourceVersion":"2.0", 81 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The "Colour Modulo" Palette Swapper 2 | 3 | **A method for O(1) palette lookups without image pre-processing.** 4 | 5 | Juju Adams 2024 6 | 7 |   8 | 9 | ## tl;dr 10 | 11 | This repo contains a library that does fast palette swapping for an arbitrary number of colours in constant time (`O(1)`) without modifying the source image like colour indexing does. This system will need a short period of time to initialize when setting up (time taken depends on the number of colours in the palette but around a millisecond for 20 colours in my limited testing). This solution hits the sweet spot between the flexibility of colour searching and the speed of colour indexing. Colour modulo palette swapping is slightly less performant than colour indexing due to the additional maths being run in the fragment shader but the colour modulo solution is much more convenient to use in production. 12 | 13 | Note that the implementation in this repo doesn't include alpha testing so isn't suitable for use in 3D games off the bat. Drop an alpha test and `discard` at the bottom of the `main()` function and you're good to go. 14 | 15 |   16 | 17 | ## Introduction 18 | 19 | Something that came up at work this week was the topic of palette swapping. This is a common technique whereby a set of colours in a source image are swapped out for a different colour. It reduces the number of assets that need to be created by allowing things like changing costume colour to be done programmatically. This technique has a long history and dates back to hardware where a game's colour palette was a dedicated space in memory and changing the palette would change the appearance of sprites drawn to the screen. Palette swapping can be used with high res assets - The Swords Of Ditto used a palette swapper, for example - but it's most commonly associated with pixel art games. 20 | 21 | You can do palette swapping by splitting an image into many layers and then tinting each layer but this results in a lot of overdraw and its use is limited. Palette swapping is most often accomplished in a fragment (pixel) shader and this is the focus of this particular article. Mapping between input and output colours for a palette swapper are assumed to be 1:1 with no tolerance/threshold values i.e. "old school" or "hard" palette swapping. (Soft palette swapping using tolerances/thresholds is useful for dealing with high res images but that's a topic for another time.) 22 | 23 | A note on terminology: I'll be using the word "colour" a lot. It's going to get a bit repetitive but there's no way around that. There are three types of colour that a palette swapper concerns itself with. Firstly, there are the "input" colours. These are colours found by sampling the image that's being drawn. Secondly, there are "target" colours which are the colours we're looking to replace. Not all colours in an image are going to be target colours, a classic example is the whites of a character's eyes. Finally, we have "output" colours. There are the colours that we are using as the replacements. Output colours are typically grouped together in palettes that an artist predefines, though some games allow a player to define their own colours. In short, input colours that match target colours get turned into the equivalent output colours. 24 | 25 | |Name |Performance |VRAM Usage|Time Complexity|Maximum Colours |Requires preprocessing| 26 | |-----------------------|----------------------|----------|---------------|-------------------------|----------------------| 27 | |Colour Indexing |Very fast |Very low |Constant |Unlimited |Yes | 28 | |ColorMod |Fast |Low |Constant |Unknown, > 30 |No | 29 | |Colour search (array) |Variable, usually OK |Zero |Linear |Depends on GPU, around 10|No | 30 | |Colour search (texture)|Variable, usually slow|Very low |Linear |Unlimited |No | 31 | |Look-up Table (LUT) |Very fast |Very high |Constant |Unlimited |No | 32 | 33 |   34 | 35 | ## Colour Searching 36 | 37 | Let's start with the most basic type of palette swapper: an iterative searcher. This sort of palette swapper, for every texel sampled from the image, iterates over all the target colours until a match is found. Once a match is found, the fragment shader chooses the associated output colour and outputs that. Actually doing this in a shader is pretty easy - send in two arrays of colours (one for the targets and one for the outputs), set up a for-loop, spin round the for-loop until you find a matching colour, output the output colour. 38 | 39 | The problems creep in fast though. As you add more colours to the array you'll find that rendering starts to get bogged down. This is because this method is what's called "`O(n)` complex", also known as "linear time complexity". Linear time complexity isn't the slowest kind of algorithm but it isn't exactly *good* either. We want to improve on this (and, indeed, we can). In addition to a larger array making things slower, you'll also find that after a certain point you can't keep adding new colours to the array. Or, at least, you can but the shader doesn't seem to be recognising those colours. That's because shaders can only cope with so much information being sent via uniforms. Sometimes, especially on lower-end hardware, you'll find that extra information sent via uniforms is just cut off (or, worse yet, the shader up and crashes). 40 | 41 | There are some advantages to colour searching as a basic palette swapper. Firstly, it's simple, based on straight-forward ideas, and requires little maintainence. It's easy to set up too and there's no tooling required as the bulk of the work is done at runtime. If you've only got two or three colours to replace this method is a-ok and it'll take you a short distance which is often good enough. But sooner or later you're going to want a bit more power. 42 | 43 |   44 | 45 | ## Colour Indexing 46 | 47 | Instead of iterating over an array of colours, smart developers can instead use "colour indexing". This method requires replacing input colours in the image itself before compiling the game. Instead of seeing a colour in an image as an actual colour, the "colour index" skips a search step entirely and encodes the array index into the colour value itself. Colours in the image that are targets are replaced with (typically) a greyscale value. Each greyscale value is actually an array index value that line up with an output colour array. 48 | 49 | For example, if you wanted to swap red to blue then you'd process your image to replace every red pixel with a greyscale value. Let's say red is the fourth target colour we have so its colour becomes #030303 (since we're zero-indexed). When drawing this image, you'd use a shader that takes greyscale images and turns it into an index value for an array. #030303 would become `3`. This index is then used to read an output colour from an array sent into the shader. This is really fast and a conceptually elegant technique. We can think of making the pre-processing step as "pre-searching" for an array index and then storing that result for fast access later. 50 | 51 | Because the input colour is the array index, the time complexity for finding the correct output colour is `O(1)`. It doesn't matter how many target colours we have, the performance of this method will stay the same, more or less. 52 | 53 | The problem with colour indexing is that it requires pre-processing all your images that need colour swapping. It's not a trivial task to write tooling to do that. You also need to ensure that colour indexes match the correct position in the output colour array across all images which is a lot of work to manage and regularly breaks. Finally, if your art assets change (which is often) then you'll need to process your art all over again. And if you've processed all your art then you really should test all your art in-game to make sure nothing is broken. The colour index method is very fast however it creates a lot of fiddly work that is liable to break, time-consuming to test, and very obvious to players when it's broken. 54 | 55 |   56 | 57 | ## Colour Modulo 58 | 59 | What we want is the speed of colour indexing with the convenience of colour searching. Whilst it's not possible to be quite as memory efficient as either of the two techniques discussed, we can use some simple maths to get both speed and convenience without too much compromise. 60 | 61 | Palette swapping is all about taking an input colour finding a matching target colour in an array. The "colour search" solution discovers our array index by iterating over all the target colours and checking for a match one by one. The "colour index" solution treats the input colour itself as an array index, albeit after pre-processing the image before compile. The "colour modulo" solution being introduced here will calculate the array index for a given input colour at runtime by using the **modulo** mathematical function. (I won't explain what modulo does in detail here because if you're implementing a palette swap shader you probably know already. If you don't, [Khan Academy](https://www.khanacademy.org/computing/computer-science/cryptography/modarithmetic/a/what-is-modular-arithmetic) has a decent article.) 62 | 63 | What're going to do is calculate the array index as simply `arrayIndex = inputColour mod base`. Because we're using modulo, we know that calculated array indexes can never exceed the base of the modulo function. This puts a hard limit on the maximum length of the array. We further choose an appropriate base such that none of the calculated array indexes overlap for the input colours in the image. We want to choose the base that gives us the smallest modulo base possible which means our final array can be as small as possible. We're going to assume all input colours are remappable for the purposes of choosing a base otherwise we might end up in a situation where we have an array index collision leading to the wrong colours being swapped. 64 | 65 | Getting an output colour is as simple as `outputColour = array[inputColour mod base]`. This is fast for the GPU to calculate in a fragment shader and doesn't require any image pre-processing. We can do some additional work to improve on this equation to reduce the array size a little bit but both synthetic tests and real world use has demonstrated to me that it's not worth the extra effort. 66 | 67 | Actually finding the right modulo base is a process of brute force, or at least I haven't found a good way of finding the best base without an exaustive search. As a result, this isn't a quick process and takes enough time that you won't want to be doing it every frame. The good news is that it only needs to happen once per target colour array and any results can be cached for use later. Potentially these results can even be pre-computed before compiling the game. 68 | 69 | Something to point out here is that the array indexes generated by this method will be sparse, unordered, and non-consecutive. Part of the trade-off of using the modulo solution is not having 100% memory efficiency and there is typically some space between entries in the array. In reality this doesn't matter and an implementation of the colour modulo technique will hide the quirky nature of the target and output colour arrays from the user. 70 | 71 |   72 | 73 | ## Look-up Textures 74 | 75 | I've been using the word "array" a lot but a colour modulo solution will regularly require an array that is larger than a shader can support (the same often applies to colour index solutions too in practice). If you use an array to contain output colours then, due to the amount of empty space that is typical in the output colour array, you'll hit a limit on how many uniform registers you can use. To make matters worse, where that limit is depends on the hardware you're testing on. In reality, an array is unlikely to be suitable to contain the output colours. Instead, we can use a "look-up texture" instead of an array, also called a "LUT". Look-up textures are slower to access than an array but without them the colour modulo solution wouldn't be viable. 76 | 77 | In GameMaker, we could either use a sprite to contain this look-up texture or - more practically - we can use a surface. We make the look-up texture available to the palette swap shader by binding it as a sampler. For the implementation in this repo, each row of the surface is an entry in an array where the index of the array is the y-axis in the surface. Multiple palette can be stored on the surface by using different columns, effectively making the surface a 2D array. 78 | 79 |   80 | 81 | ## GLSL ES 1.00 Makes Life Hard 82 | 83 | *If you're not using GameMaker (And you still found this repo! Hi there) then you can skip this bit and use a straight-forward implementation. You'll be fine.* 84 | 85 | Whilst the colour modulo technique is sound in principle, unfortunately GameMaker's humiliatingly old version of GLSL ES prevents us from using integer modulo in a shader. This means that we have to rely on floating point numbers to be precise when working with integers. Floating point numbers are well known to have devious accuracy and precision issues. Older mobile GPUs especially have problems with this - even when forcing high precision such that we're stuffing a 24-bit integer into a 32-bit float you'll still often run into problems. The naive implementation of colour modulo is thus unlikely to work reliably in the wild. Fortunately there are some fun modular arithmetic tricks we can do to work around loss of precision. 86 | 87 | Here's the naive implementation where we use floats instead of integers: 88 | 89 | ``` 90 | vec4 inputSample = texture2D(gm_BaseTexture, v_vTexcoord); 91 | float colourInteger = (255.0*inputSample.r) + (256.0*255.0*inputSample.g) + (256.*256.0*255.0*inputSample.b); 92 | float moduloValue = mod(colourInteger, u_fModulo); 93 | ``` 94 | 95 | Rewriting this to work around precision issues isn't immediately obvious but if we apply the following identities then we can start to get somewhere: 96 | 97 | ``` 98 | Addition: 99 | (X + Y) mod A = ((X mod A) + (Y mod A)) mod A 100 | 101 | Addition (3) 102 | (X + Y + Z) mod A = ((X mod A) + (Y mod A) + (Z mod A)) mod A 103 | 104 | Multiplication: 105 | (X * Y) mod A = ((X mod A) * (Y mod A)) mod A 106 | 107 | Final Equation: 108 | (255*Red + 256*255*Green + 256*256*255*Blue) mod A = (I*(Red mod A) + J*(Green mod A) + K*(Blue mod A)) mod A 109 | where I = (255 mod A) 110 | where j = (256*255 mod A) 111 | where K = (256*256*255 mod A) 112 | ``` 113 | 114 | Now we've broken down the problem into a set of small modulo operations that are operating over a smaller range of values then we're far more likely to be working within the precision limits that lower-than-high precision floats afford us. Here's what the actual GLSL code looks like in practice: 115 | 116 | ``` 117 | vec4 inputSample = texture2D(gm_BaseTexture, v_vTexcoord); 118 | vec3 moduloVector = u_vModulo.rgb*modV(255.0*inputSample.rgb, u_vModulo.a); 119 | float moduloValue = mod(moduloVector.r + moduloVector.g + moduloVector.b, u_vModulo.a); 120 | ``` 121 | 122 | There're two new tokens introduced here: `modV()` and `moduloVector`. `modV()` is just a function that applies a modulo per component of a vector which some extra rounding to cope with near-integer floating point numbers. `u_vModulo` is a bit spicier. Its `a` component is the colour modulo as described in previous sections of this article. The `rgb` components are the `IJK` terms calculated above. By describing these three terms as a 3-component vector we can make this shader a little more efficient which is always helpful. We calculate `u_vModulo` outside the shader for convenience as well as to avoid any further precision problems. Note that the factor of `255.0` has been moved around - this seems to lead to better stability in edge cases from my limited testing. Your mileage may vary. 123 | 124 |   125 | 126 | ## Putting It All Together 127 | 128 | Once a suitable modulo has been found, we've written some code to read colours out from a look-up texture, and the precision issues have been sorted out we're pretty much done. The only thing left to do is write a nice API around this thing and sling it into a project. I won't go into much detail here about what a decent API would look like because I've written a reference implementation with such an API already. Something to note is that I've found adding a nice debug mode to the palette swapper to be beneficial to quickly identify missing or undefined colours. I recommend doing the same. 129 | 130 | Further work in this area may be related to using a larger look-up texture, perhaps a similar size to a colour grading LUT, to cope with palette swapping for non-pixel art graphics. 131 | 132 | At any rate, colour modulo has been useful in my workplace to alleviate the workload on artists whilst still achieving the same open-ended goals with good performance. Use it well. 133 | -------------------------------------------------------------------------------- /scripts/ColorMod/ColorMod.gml: -------------------------------------------------------------------------------- 1 | // Feather disable all 2 | 3 | /// `ColorMod(targetColorArray, [maxPalettes=30], [debugMode=false]) constructor` 4 | /// 5 | /// Constructor for a ColorMod struct. This struct acts as an interface for customising and 6 | /// enabling palette swaps using the "color modulo" technique. A ColorMod struct has no public 7 | /// variables. A full list of public methods that should be used are listed below. Basic use is: 8 | /// 9 | /// Step 1: Create a ColorMod struct at the start of the game 10 | /// global.colorModForPlayer = ColorMod([#a084f1, #8966ea, #6759a4, #524280]); 11 | /// 12 | /// Step 2: Adds palettes to the ColorMod struct 13 | /// global.colorModForPlayer.PaletteAdd("green", [#82cc71, #68b656, #539f42, #479234]); 14 | /// global.colorModForPlayer.PaletteAdd("blue", [#2aaaf3, #249ee3, #2992ce, #1f7db3]); 15 | /// 16 | /// Step 3: In a Draw event, set the ColorMod shader, draw a sprite, then reset the shader 17 | /// global.colorModForPlayer.SetShader(paletteName); 18 | /// draw_sprite(sPlayer, 0, x, y); 19 | /// shader_reset(); 20 | /// 21 | /// 22 | /// 23 | /// If the `debugMode` parameter is set to `true` then any colors in the drawn image that are not 24 | /// in the default palette will usually be highlighted in bright fuchsia. Drawing with debug mode 25 | /// turned on will significantly decrease performance so remember to turn it off before compiling 26 | /// your game for other people to play. 27 | /// 28 | /// The `moduloHint` parameter allows you to provide a pre-calculated modulo value. This skips the 29 | /// slow modulo calculation step when first creating the ColorMod struct. You should calculate a 30 | /// modulo value by running the game then copying that modulo value into your codebase for 31 | /// subsequent runs of the game. You will need to update the modulo hint if your default palette 32 | /// changes (but not alternate palettes). 33 | /// 34 | /// N.B. This function is fairly slow and you should generally only call this function once when 35 | /// the game boots. 36 | /// 37 | /// N.B. ColorMod is not compatible with antialiased art or art drawn with texture filtering / 38 | /// bilinear interpolation switched on. ColorMod should only be used with pixel art. 39 | /// 40 | /// 41 | /// 42 | /// .Destroy() 43 | /// Destroys the ColorMod struct, freeing any memory associated with it. 44 | /// 45 | /// N.B. You must call this function when you've finished using a ColorMod struct otherwise you 46 | /// will experience memory leaks that will eventually crash your game. 47 | /// 48 | /// .SetShader(paletteName) 49 | /// Sets up the palette swap shader for the given palette. 50 | /// 51 | /// .SetShaderBlend(paletteNameA, paletteNameB, blendFactor) 52 | /// Sets up the palette swap shader to blend between two palettes. `blendFactor` should be a 53 | /// number between 0 and 1. 54 | /// 55 | /// .SetShaderIndex(paletteIndex) 56 | /// Sets up the palette swap shader for the given palette index. You can blend between two 57 | /// palettes by using a fractional value e.g `1.5` is a 50% blend palette index 1 and 2. A 58 | /// palette's index can be found by calling `.PaletteGetIndex()`. 59 | /// 60 | /// .PaletteAdd(paletteName, colorArray) 61 | /// Adds a new palette to the ColorMod struct. If the color array is too short, colors will be 62 | /// copied from the target colors used to create to fill in the gap. If a palette with the 63 | /// given name already exists, this function will throw an error. 64 | /// 65 | /// .PaletteGetIndex(paletteName) 66 | /// Returns the index for the named palette. 67 | /// 68 | /// .PaletteOverwrite(paletteName, colorArray, destOffset, [srcOffset], [length]) 69 | /// Overwrites a portion of an existing palette using part of a source color array. If a 70 | /// palette with the provided name does not exist, this function with throw an error. 71 | /// 72 | /// .PaletteEnsure(paletteName, [colorArray]) 73 | /// Ensures that a palette exists with the given name and using the given color array. This 74 | /// means a new palette will be created if necessary or an existing palette will be overwritten. 75 | /// 76 | /// .PaletteClear(paletteName) 77 | /// Clears the contents of a palette, using the original target colors used to create the 78 | /// ColorMod struct. 79 | /// 80 | /// .PaletteGet(paletteName) 81 | /// Returns the color array being used for the given palette. 82 | /// 83 | /// .PaletteRemove(paletteName) 84 | /// Removes a palette from the ColorMod struct. 85 | /// 86 | /// .PaletteExists(paletteName) 87 | /// Returns if a palette has been added for the ColorMod struct. 88 | /// 89 | /// .PaletteCount() 90 | /// Returns the number of palettes added to the ColorMod struct. 91 | /// 92 | /// .RemoveAll() 93 | /// Removes all palettes for the ColorMod struct. 94 | /// 95 | /// .GetModulo() 96 | /// Returns the modulo value for the ColorMod struct, chosen by analysis of the target colors 97 | /// provided when creating the ColorMod struct. 98 | /// 99 | /// .GetColorCount() 100 | /// Returns the number of target colors provided when creating the ColorMod struct. 101 | /// 102 | /// .EnsureSurface() 103 | /// Ensures that the ColorMod struct has generated its internal palette surface. This can help 104 | /// with hitching when a ColorMod struct is first used. 105 | /// 106 | /// .MarkDirty() 107 | /// Marks the ColorMod struct palette surface as "dirty" which will trigger a redraw when 108 | /// .SetShader() is next called. 109 | /// 110 | /// .DebugDrawPalette(x, y, scale) 111 | /// Draws the entire palette at the given coordinates and with the given scale. 112 | /// 113 | /// .DebugDrawOutput(x, y, width, height, paletteName, colorIndex) 114 | /// Draws the color value for the given palette and color index, stretched out at the given 115 | /// coordinates. Useful for checking values on the palette surface are what you expect them 116 | /// to be. 117 | /// 118 | /// 119 | /// 120 | /// @param targetColorArray 121 | /// @param [maxPalettes=30] 122 | /// @param [debugMode=false] 123 | /// @param [moduloHint] 124 | 125 | show_debug_message("ColorMod: Welcome to ColorMod by Juju Adams! This is version 1.3.0, 2024-10-27"); 126 | 127 | function ColorMod(_targetColorArray, _maxPalettes = 30, _debugMode = false, _moduloHint = undefined) constructor 128 | { 129 | static _moduloLookup = {}; 130 | 131 | __targetColorArray = variable_clone(_targetColorArray); 132 | __maxPalettes = _maxPalettes; 133 | __debugMode = _debugMode; 134 | 135 | __colorCount = array_length(__targetColorArray); 136 | 137 | //Skip searching for a suitable modulo for this set of colors if we can 138 | var _searchArray = variable_clone(_targetColorArray); 139 | array_sort(_searchArray, true); 140 | var _searchKey = json_stringify(_searchArray); 141 | var _modulo = _moduloLookup[$ _searchKey]; 142 | 143 | if (_modulo == undefined) 144 | { 145 | //Welp, didn't find a solution 146 | var _colorCount = __colorCount; 147 | 148 | if (_moduloHint != undefined) 149 | { 150 | //If we have a hint, check that it works 151 | 152 | var _success = true; 153 | var _foundDict = {}; 154 | 155 | var _i = 0; 156 | repeat(_colorCount) 157 | { 158 | var _value = _targetColorArray[_i] mod _moduloHint; 159 | if (variable_struct_exists(_foundDict, _value)) 160 | { 161 | _success = false; 162 | break; 163 | } 164 | 165 | _foundDict[$ _value] = true; 166 | 167 | ++_i; 168 | } 169 | 170 | if (_success) 171 | { 172 | _modulo = _moduloHint; 173 | } 174 | } 175 | 176 | if (_modulo == undefined) 177 | { 178 | //Do a brute force search instead 179 | 180 | var _duplicateDict = {}; 181 | 182 | var _modulo = _colorCount-1; 183 | do 184 | { 185 | var _success = true; 186 | var _foundDict = {}; 187 | var _seenDict = {}; 188 | ++_modulo; 189 | 190 | var _i = 0; 191 | repeat(_colorCount) 192 | { 193 | var _color = _targetColorArray[_i]; 194 | 195 | if (variable_struct_exists(_seenDict, _color)) 196 | { 197 | if (not variable_struct_exists(_duplicateDict, _color)) 198 | { 199 | var _bgr = ((_color & 0x0000FF) << 16) | (_color & 0x00FF00) | ((_color & 0xFF0000) >> 16); 200 | show_debug_message($"ColorMod: Warning! Found duplicate color in default palette #{string_delete(string(ptr(_bgr)), 1, 10)}"); 201 | 202 | _duplicateDict[$ _color] = true; 203 | } 204 | } 205 | else 206 | { 207 | var _value = _color mod _modulo; 208 | 209 | if (variable_struct_exists(_foundDict, _value)) 210 | { 211 | _success = false; 212 | break; 213 | } 214 | 215 | _foundDict[$ _value] = true; 216 | _seenDict[$ _color] = true; 217 | } 218 | 219 | ++_i; 220 | } 221 | } 222 | until(_success) 223 | 224 | if (_moduloHint != undefined) show_debug_message($"ColorMod: Warning! Modulo hint {_moduloHint} invalid, using modulo {_modulo} instead"); 225 | } 226 | 227 | _moduloLookup[$ _searchKey] = _modulo; 228 | } 229 | 230 | __modulo = _modulo; 231 | 232 | __width = __maxPalettes; 233 | __height = __modulo; 234 | 235 | __dirty = true; 236 | __surface = -1; 237 | __destroyed = false; 238 | __texture = undefined; 239 | __texelWidth = undefined; 240 | __texelHeight = undefined; 241 | 242 | __outputPaletteArray = []; 243 | __outputPaletteDict = {}; 244 | 245 | 246 | 247 | 248 | 249 | static PaletteAdd = function(_paletteName, _outputColorArray = undefined) 250 | { 251 | if (_outputColorArray == undefined) 252 | { 253 | _outputColorArray = variable_clone(__targetColorArray); 254 | } 255 | 256 | if (array_length(_outputColorArray) != __colorCount) 257 | { 258 | __Error("Color array length (", array_length(_outputColorArray), ") doesn't match target color count ", __colorCount); 259 | return self; 260 | } 261 | 262 | if (variable_struct_exists(__outputPaletteDict, _paletteName)) 263 | { 264 | __Error("Palette \"", _paletteName, "\" already exists"); 265 | return self; 266 | } 267 | 268 | var _count = array_length(__outputPaletteArray); 269 | if (_count >= __maxPalettes) 270 | { 271 | __Error("Cannot add palette \"", _paletteName, "\", run out of palette slots (max=", __maxPalettes, ")"); 272 | return self; 273 | } 274 | 275 | var _data = { 276 | __name: _paletteName, 277 | __index: _count, 278 | __colorArray: _outputColorArray, 279 | }; 280 | 281 | __outputPaletteDict[$ _paletteName] = _data; 282 | array_push(__outputPaletteArray, _data); 283 | 284 | __dirty = true; 285 | 286 | return self; 287 | } 288 | 289 | static PaletteGetIndex = function(_paletteName) 290 | { 291 | var _data = __outputPaletteDict[$ _paletteName]; 292 | if (_data == undefined) 293 | { 294 | __Error("Palette \"", _paletteName, "\" not found"); 295 | return; 296 | } 297 | 298 | return _data.__index; 299 | } 300 | 301 | static PaletteEnsure = function(_paletteName, _outputColorArray = undefined) 302 | { 303 | if (variable_struct_exists(__outputPaletteDict, _paletteName)) 304 | { 305 | PaletteOverwrite(_paletteName, _outputColorArray); 306 | } 307 | else 308 | { 309 | PaletteAdd(_paletteName, _outputColorArray); 310 | } 311 | } 312 | 313 | static PaletteOverwrite = function(_paletteName, _outputColorArray = __targetColorArray, _destOffset = 0, _srcOffset = 0, _length = array_length(_outputColorArray)) 314 | { 315 | var _data = __outputPaletteDict[$ _paletteName]; 316 | if (_data == undefined) 317 | { 318 | __Error("Palette \"", _paletteName, "\" doesn't exist"); 319 | return self; 320 | } 321 | 322 | if (_destOffset + _length > __colorCount) 323 | { 324 | __Error("Overwrite operation would copy too many colors (dest=", _destOffset, ", length=", _length, ", color count=", __colorCount, ")"); 325 | return self; 326 | } 327 | 328 | array_copy(_data.__colorArray, _destOffset, _outputColorArray, _srcOffset, _length); 329 | 330 | __dirty = true; 331 | 332 | return self; 333 | } 334 | 335 | static PaletteClear = function(_paletteName) 336 | { 337 | var _data = __outputPaletteDict[$ _paletteName]; 338 | if (_data == undefined) return; 339 | 340 | array_copy(_data.__colorArray, 0, __targetColorArray, 0, __colorCount); 341 | 342 | __dirty = true; 343 | 344 | return self; 345 | } 346 | 347 | static PaletteGet = function(_paletteName) 348 | { 349 | var _data = __outputPaletteDict[$ _paletteName]; 350 | return (_data == undefined)? undefined : _data.__colorArray; 351 | } 352 | 353 | static PaletteRemove = function(_paletteName) 354 | { 355 | var _outputPaletteArray = __outputPaletteArray; 356 | 357 | var _data = __outputPaletteDict[$ _paletteName]; 358 | var _index = _data.__index; 359 | 360 | variable_struct_remove(__outputPaletteDict, _paletteName); 361 | array_delete(_outputPaletteArray, _index, 1); 362 | 363 | var _i = _index; 364 | repeat(array_length(_outputPaletteArray) - _index - 1) 365 | { 366 | _outputPaletteArray[_i].__index = _i; 367 | ++_i; 368 | } 369 | 370 | __dirty = true; 371 | 372 | return self; 373 | } 374 | 375 | static PaletteExists = function(_paletteName) 376 | { 377 | return variable_struct_exists(__outputPaletteDict, _paletteName); 378 | } 379 | 380 | static PaletteCount = function() 381 | { 382 | return array_length(__outputPaletteArray); 383 | } 384 | 385 | static RemoveAll = function() 386 | { 387 | array_resize(__outputPaletteArray, 0); 388 | __outputPaletteDict = {}; 389 | 390 | __dirty = true; 391 | 392 | return self; 393 | } 394 | 395 | static Destroy = function() 396 | { 397 | __destroyed = true; 398 | 399 | if (surface_exists(__surface)) 400 | { 401 | surface_free(__surface); 402 | __surface = -1; 403 | } 404 | } 405 | 406 | static SetShader = function(_paletteName) 407 | { 408 | if (__destroyed) return; 409 | 410 | static _u_sPalette = shader_get_sampler_index(__shdColorMod, "u_sPalette"); 411 | static _u_vModulo = shader_get_uniform(__shdColorMod, "u_vModulo"); 412 | static _u_fColumn = shader_get_uniform(__shdColorMod, "u_fColumn"); 413 | static _u_vTexel = shader_get_uniform(__shdColorMod, "u_vTexel"); 414 | 415 | static _u_sPaletteDebug = shader_get_sampler_index(__shdColorModDebug, "u_sPalette"); 416 | static _u_vModuloDebug = shader_get_uniform(__shdColorModDebug, "u_vModulo"); 417 | static _u_fColumnDebug = shader_get_uniform(__shdColorModDebug, "u_fColumn"); 418 | static _u_vTexelDebug = shader_get_uniform(__shdColorModDebug, "u_vTexel"); 419 | 420 | var _data = __outputPaletteDict[$ _paletteName]; 421 | if (_data == undefined) 422 | { 423 | __Error("Palette \"", _paletteName, "\" not found"); 424 | return; 425 | } 426 | 427 | EnsureSurface(); 428 | 429 | if (__debugMode) 430 | { 431 | shader_set(__shdColorModDebug); 432 | texture_set_stage(_u_sPaletteDebug, __texture); 433 | shader_set_uniform_f(_u_vModuloDebug, 1, 0x100 mod __modulo, 0x10000 mod __modulo, __modulo); 434 | shader_set_uniform_f(_u_fColumnDebug, _data.__index + 1); 435 | shader_set_uniform_f(_u_vTexelDebug, __texelWidth, __texelHeight); 436 | } 437 | else 438 | { 439 | shader_set(__shdColorMod); 440 | texture_set_stage(_u_sPalette, __texture); 441 | shader_set_uniform_f(_u_vModulo, 1, 0x100 mod __modulo, 0x10000 mod __modulo, __modulo); 442 | shader_set_uniform_f(_u_fColumn, _data.__index); 443 | shader_set_uniform_f(_u_vTexel, __texelWidth, __texelHeight); 444 | } 445 | } 446 | 447 | static SetShaderBlend = function(_paletteNameA, _paletteNameB, _blendFactor) 448 | { 449 | if (__destroyed) return; 450 | 451 | static _u_sPalette = shader_get_sampler_index(__shdColorModBlend, "u_sPalette"); 452 | static _u_vModulo = shader_get_uniform(__shdColorModBlend, "u_vModulo"); 453 | static _u_vColumnData = shader_get_uniform(__shdColorModBlend, "u_vColumnData"); 454 | static _u_vTexel = shader_get_uniform(__shdColorModBlend, "u_vTexel"); 455 | 456 | static _u_sPaletteDebug = shader_get_sampler_index(__shdColorModBlendDebug, "u_sPalette"); 457 | static _u_vModuloDebug = shader_get_uniform(__shdColorModBlendDebug, "u_vModulo"); 458 | static _u_vColumnDataDebug = shader_get_uniform(__shdColorModBlendDebug, "u_vColumnData"); 459 | static _u_vTexelDebug = shader_get_uniform(__shdColorModBlendDebug, "u_vTexel"); 460 | 461 | var _dataA = __outputPaletteDict[$ _paletteNameA]; 462 | if (_dataA == undefined) 463 | { 464 | __Error("Palette \"", _paletteName, "\" not found"); 465 | return; 466 | } 467 | 468 | var _dataB = __outputPaletteDict[$ _paletteNameB]; 469 | if (_dataB == undefined) 470 | { 471 | __Error("Palette \"", _paletteName, "\" not found"); 472 | return; 473 | } 474 | 475 | EnsureSurface(); 476 | 477 | if (__debugMode) 478 | { 479 | shader_set(__shdColorModBlendDebug); 480 | texture_set_stage(_u_sPaletteDebug, __texture); 481 | shader_set_uniform_f(_u_vModuloDebug, 1, 0x100 mod __modulo, 0x10000 mod __modulo, __modulo); 482 | shader_set_uniform_f(_u_vColumnDataDebug, _dataA.__index + 1, _dataB.__index + 1, clamp(_blendFactor, 0, 1)); 483 | shader_set_uniform_f(_u_vTexelDebug, __texelWidth, __texelHeight); 484 | } 485 | else 486 | { 487 | shader_set(__shdColorModBlend); 488 | texture_set_stage(_u_sPalette, __texture); 489 | shader_set_uniform_f(_u_vModulo, 1, 0x100 mod __modulo, 0x10000 mod __modulo, __modulo); 490 | shader_set_uniform_f(_u_vColumnData, _dataA.__index, _dataB.__index, clamp(_blendFactor, 0, 1)); 491 | shader_set_uniform_f(_u_vTexel, __texelWidth, __texelHeight); 492 | } 493 | } 494 | 495 | static SetShaderBlendIndex = function(_index) 496 | { 497 | if (__destroyed) return; 498 | 499 | static _u_sPalette = shader_get_sampler_index(__shdColorModBlend, "u_sPalette"); 500 | static _u_vModulo = shader_get_uniform(__shdColorModBlend, "u_vModulo"); 501 | static _u_vColumnData = shader_get_uniform(__shdColorModBlend, "u_vColumnData"); 502 | static _u_vTexel = shader_get_uniform(__shdColorModBlend, "u_vTexel"); 503 | 504 | static _u_sPaletteDebug = shader_get_sampler_index(__shdColorModBlendDebug, "u_sPalette"); 505 | static _u_vModuloDebug = shader_get_uniform(__shdColorModBlendDebug, "u_vModulo"); 506 | static _u_vColumnDataDebug = shader_get_uniform(__shdColorModBlendDebug, "u_vColumnData"); 507 | static _u_vTexelDebug = shader_get_uniform(__shdColorModBlendDebug, "u_vTexel"); 508 | 509 | EnsureSurface(); 510 | 511 | if (__debugMode) 512 | { 513 | shader_set(__shdColorModBlendDebug); 514 | texture_set_stage(_u_sPaletteDebug, __texture); 515 | shader_set_uniform_f(_u_vModuloDebug, 1, 0x100 mod __modulo, 0x10000 mod __modulo, __modulo); 516 | shader_set_uniform_f(_u_vColumnDataDebug, floor(_index) + 1, ceil(_index) + 1, frac(_index)); 517 | shader_set_uniform_f(_u_vTexelDebug, __texelWidth, __texelHeight); 518 | } 519 | else 520 | { 521 | shader_set(__shdColorModBlend); 522 | texture_set_stage(_u_sPalette, __texture); 523 | shader_set_uniform_f(_u_vModulo, 1, 0x100 mod __modulo, 0x10000 mod __modulo, __modulo); 524 | shader_set_uniform_f(_u_vColumnData, floor(_index), ceil(_index), frac(_index)); 525 | shader_set_uniform_f(_u_vTexel, __texelWidth, __texelHeight); 526 | } 527 | } 528 | 529 | static GetModulo = function() 530 | { 531 | return __modulo; 532 | } 533 | 534 | static GetColorCount = function() 535 | { 536 | return __colorCount; 537 | } 538 | 539 | static MarkDirty = function() 540 | { 541 | __dirty = true; 542 | } 543 | 544 | static DebugDrawPalette = function(_x, _y, _scale) 545 | { 546 | if (__destroyed) return; 547 | 548 | EnsureSurface(); 549 | 550 | draw_surface_ext(__surface, _x, _y, _scale, _scale, 0, c_white, 1); 551 | } 552 | 553 | static DebugDrawOutput = function(_x, _y, _width, _height, _paletteName, _colorIndex) 554 | { 555 | if (__destroyed) return; 556 | 557 | static _u_fRow = shader_get_uniform(__shdColorModDebugOutput, "u_fRow"); 558 | static _u_fColumn = shader_get_uniform(__shdColorModDebugOutput, "u_fColumn"); 559 | static _u_vTexel = shader_get_uniform(__shdColorModDebugOutput, "u_vTexel"); 560 | 561 | EnsureSurface(); 562 | 563 | shader_set(__shdColorModDebugOutput); 564 | shader_set_uniform_f(_u_fRow, __debugMode? (_colorIndex + 1) : _colorIndex); 565 | shader_set_uniform_f(_u_fColumn, __outputPaletteDict[$ _paletteName].__index); 566 | shader_set_uniform_f(_u_vTexel, __texelWidth, __texelHeight); 567 | 568 | draw_surface_stretched(__surface, _x, _y, _width, _height); 569 | 570 | shader_reset(); 571 | } 572 | 573 | static EnsureSurface = function() 574 | { 575 | static _identityMatrix = matrix_build_identity(); 576 | 577 | if (__destroyed) return; 578 | 579 | if (not surface_exists(__surface)) 580 | { 581 | __surface = surface_create(__debugMode? (__width + 1) : __width, __height); 582 | __texture = surface_get_texture(__surface); 583 | __texelWidth = texture_get_texel_width(__texture); 584 | __texelHeight = texture_get_texel_height(__texture); 585 | 586 | __dirty = true; 587 | } 588 | 589 | if (__dirty) 590 | { 591 | __dirty = false; 592 | 593 | var _oldBlendMode = gpu_get_blendmode_ext(); 594 | var _oldWorldMatrix = matrix_get(matrix_world); 595 | matrix_set(matrix_world, _identityMatrix); 596 | surface_set_target(__surface); 597 | 598 | draw_clear_alpha(c_black, 0); 599 | 600 | var _debugMode = __debugMode; 601 | var _modulo = __modulo; 602 | var _targetColorArray = __targetColorArray; 603 | var _colorCount = __colorCount; 604 | var _outputPaletteArray = __outputPaletteArray; 605 | 606 | if (__debugMode) 607 | { 608 | var _i = 0; 609 | repeat(array_length(_targetColorArray)) 610 | { 611 | var _color = _targetColorArray[_i]; 612 | draw_sprite_ext(__sColorModPixel, 0, 0, _color mod _modulo, 1, 1, 0, _color, 1); 613 | ++_i; 614 | } 615 | } 616 | 617 | var _i = 0; 618 | repeat(array_length(_outputPaletteArray)) 619 | { 620 | var _paletteData = _outputPaletteArray[_i]; 621 | var _outputColorArray = _paletteData.__colorArray 622 | 623 | var _x = _debugMode? (_i + 1) : _i; 624 | 625 | var _j = 0; 626 | repeat(_colorCount) 627 | { 628 | var _y = _targetColorArray[_j] mod _modulo; 629 | draw_sprite_ext(__sColorModPixel, 0, _x, _y, 1, 1, 0, _outputColorArray[_j], 1); 630 | ++_j; 631 | } 632 | 633 | ++_i; 634 | } 635 | 636 | gpu_set_blendmode_ext(_oldBlendMode[0], _oldBlendMode[1]); 637 | matrix_set(matrix_world, _oldWorldMatrix); 638 | surface_reset_target(); 639 | } 640 | } 641 | 642 | static __Error = function() 643 | { 644 | var _string = "ColorMod:\n"; 645 | 646 | var _i = 0; 647 | repeat(argument_count) 648 | { 649 | _string += string(argument[_i]); 650 | ++_i; 651 | } 652 | 653 | show_error(_string + "\n ", true); 654 | } 655 | } --------------------------------------------------------------------------------