{"record":{"id":"cb74fc48cddf6c9c","repo":"Anuken/Mindustry","slug":"image-dimensions-are-not-divisible-by-4-width-x","errorCode":null,"errorMessage":"Image dimensions are not divisible by 4: {width}x{height}","messagePattern":"Image dimensions are not divisible by 4: (.+?)x(.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"tools/src/mindustry/tools/ImageTileGenerator.java","lineNumber":22,"sourceCode":"import arc.graphics.*;\nimport arc.math.geom.*;\nimport arc.struct.*;\nimport arc.util.serialization.*;\n\nimport java.io.*;\n\n/** Java port of https://github.com/GglLfr/tile-gen/blob/master/src/main.rs */\npublic class ImageTileGenerator{\n    private static final int layoutWidth = 384, layoutHeight = 128;\n    private static final Pixmap layout = new Pixmap(Base64Coder.decode(\"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\"));\n\n    public static void generate(Fi path, String name, Fi outputDir) throws IOException{\n        Pixmap image = new Pixmap(path);\n\n        try{\n            int width = image.width, height = image.height;\n\n            if(width % 4 != 0 || height % 4 != 0) throw new IOException(\"Image dimensions are not divisible by 4: \" + width + \"x\" + height);\n            if(width != height) throw new IOException(\"Image is not square: \" + width + \"x\" + height);\n\n            int cellSize = width / 4;\n\n            IntIntMap colorToPosition = new IntIntMap();\n\n            for(int x = 0; x < 4; x++){\n                for(int y = 0; y < 4; y++){\n                    colorToPosition.put(layout.get(x * layoutWidth / 12, y * layoutHeight / 4), Point2.pack(x * width / 4, y * height / 4));\n                }\n            }\n\n            int outWidth = width / 4 * 12, outHeight = height;\n            Pixmap out = new Pixmap(outWidth, outHeight);\n\n            for(int cx = 0; cx < 12; cx++){\n                for(int cy = 0; cy < 4; cy++){\n                    for(int rx = 0; rx < cellSize; rx++){","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/Anuken/Mindustry/blob/f695ad7e60323ebced984fa26d0bcf0bc54296b4/tools/src/mindustry/tools/ImageTileGenerator.java#L4-L40","documentation":"Thrown by ImageTileGenerator.generate when the source pixmap's width or height is not a multiple of 4. The generator slices the image into a 4x4 grid of cells (cellSize = width/4) and samples a layout pixmap at corresponding positions; non-divisible dimensions break that cell math, so the input is rejected before processing begins. Note the next guard also requires width == height, so the image must additionally be square.","triggerScenarios":"Calling generate() with an image whose dimensions are not divisible by 4 (e.g. 1000x1000, 512x768, 500x504). The very next line also rejects non-square images, so both conditions must hold.","commonSituations":"An artist supplies a texture at a non-standard resolution; the wrong source file is fed to the generator; an image was resized/exported with dimensions that don't align to the 4-cell grid; a tool exported at retina/non-integer scale.","solutions":["Resize or pad the source image so both dimensions are multiples of 4 (and equal, since square is also required).","Regenerate the source asset at a compliant resolution such as 1024x1024 or 2048x2048.","Add a pre-check in the asset pipeline that validates dimensions before invoking generate().","Document the dimension contract next to the generator so artists produce compliant sources."],"exampleFix":"// before\nPixmap image = new Pixmap(path);\nif(image.width % 4 != 0 || image.height % 4 != 0) throw new IOException(\"Image dimensions are not divisible by 4: \" + image.width + \"x\" + image.height);\n\n// after (pre-validate and resize the source asset before calling generate)\nif(width % 4 != 0 || height % 4 != 0 || width != height){\n    int side = Math.max(width, height);\n    side = ((side + 3) / 4) * 4; // round up to next multiple of 4\n    throw new IOException(\"Source must be a square multiple of 4 (e.g. \" + side + \"x\" + side + \"); got \" + width + \"x\" + height);\n}","handlingStrategy":"validation","validationCode":"// Before calling ImageTileGenerator.generate\nPixmap check = new Pixmap(path);\nint w = check.width, h = check.height;\nif(w % 4 != 0 || h % 4 != 0 || w != h){\n    throw new IllegalArgumentException(\"Source must be a square multiple of 4; got \" + w + \"x\" + h);\n}","typeGuard":"static boolean isTileable(Pixmap p){\n    return p.width % 4 == 0 && p.height % 4 == 0 && p.width == p.height;\n}","tryCatchPattern":null,"preventionTips":["Validate image dimensions in the asset pipeline before invoking generate().","Standardize source assets on square power-of-two resolutions that are always divisible by 4.","Document the dimension contract (square, multiple of 4) at the generator entry point for artists."],"tags":["tools","assets","validation","build"],"backgroundTag":null,"analyzedSha":"f695ad7e60323ebced984fa26d0bcf0bc54296b4","analyzedAt":"2026-08-14T04:31:16.262Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}