<commit_before>import nimPNG
import os, osproc, json, strutils, times, sequtils, tables, algorithm, math

import nimx.rect_packer
import nimx.types
import nimx.pathutils

import imgtools.imgtools

type Rect = imgtools.Rect
type Point = tuple[x, y: int32]
type Size = tuple[width, height: int]

const consumeLessMemory = defined(windows)

type
    ImageOccurence = object
        parentComposition, parentNode, parentComponent: JsonNode
        frameIndex: int
        compPath: string
        originalTranslationInNode: tuple[x, y: float]

    SpriteSheetImage = ref object
        spriteSheet: SpriteSheet # Index of sprite sheet in tool.images array
        srcBounds: Rect
        actualBounds: Rect
        originalPath: string
        srcSize: Size
        targetSize: Size
        pos: Point
        png: PNGResult
        occurences: seq[ImageOccurence]

    SpriteSheet = ref object
        index: int # Index of sprite sheet in tool.images array
        images: seq[SpriteSheetImage]
        packer: RectPacker

proc newSpriteSheetImage(path: string): SpriteSheetImage =
    result.new()
    result.originalPath = path
    result.occurences = @[]

proc newSpriteSheet(minSize: Size): SpriteSheet =
    result.new()
    let px = max(nextPowerOfTwo(minSize.width), 1024)
    let py = max(nextPowerOfTwo(minSize.height), 1024)
    result.packer = newPacker(px.int32, py.int32)
    result.packer.maxX = px.int32
    result.packer.maxY = py.int32
    result.images = newSeq[SpriteSheetImage]()

type ImgTool* = ref object
    compositionPaths*: seq[string]
    compositions: seq[JsonNode]
    outPrefix*: string #
    originalResPath*: string #
    resPath*: string #
    compressOutput*: bool
    images: Table[string, SpriteSheetImage]
    spriteSheets: seq[SpriteSheet]

    latestOriginalModificationDate: Time


proc newImgTool*(): ImgTool =
    result.new()
    result.images = initTable[string, SpriteSheetImage]()
    result.spriteSheets = newSeq[SpriteSheet]()

proc withSpriteNodes(n: JsonNode, p: proc(j, s: JsonNode)) =
    let components = n["components"]
    if not components.isNil:
        let sprite = components["Sprite"]
        if not sprite.isNil:
            p(n, sprite)
    let children = n["children"]
    if not children.isNil:
        for c in children:
            withSpriteNodes(c, p)

proc tryPackImage(ss: SpriteSheet, im: SpriteSheetImage): bool =
    im.pos = ss.packer.packAndGrow(im.targetSize.width.int32, im.targetSize.height.int32)
    result = im.pos.hasSpace
    if result:
        ss.images.add(im)
        im.spriteSheet = ss

proc composeAndWrite(tool: ImgTool, ss: SpriteSheet, path: string) =
    var data = newString(ss.packer.width * ss.packer.height * 4)
    for im in ss.images:
        if im.png.isNil:
            im.png = loadPNG32(im.originalPath)

        if im.srcSize == im.targetSize:
            blitImage(
                data, ss.packer.width, ss.packer.height, # Target image
                im.pos.x, im.pos.y, # Position in target image
                im.png.data, im.png.width, im.png.height,
                im.srcBounds.x, im.srcBounds.y, im.srcBounds.width, im.srcBounds.height)
        else:
            resizeImage(im.png.data, im.png.width, im.png.height,
                data, ss.packer.width, ss.packer.height,
                im.srcBounds.x, im.srcBounds.y, im.srcBounds.width, im.srcBounds.height,
                im.pos.x, im.pos.y, im.targetSize.width, im.targetSize.height)

        im.png = nil # We no longer need the data in memory

    discard savePNG32(path, data, ss.packer.width, ss.packer.height)
    if tool.compressOutput:
        discard execCmd("pngquant --force --speed 1 -o " & path & " " & path)

    when consumeLessMemory:
        GC_fullCollect()

proc adjustTranslationValueForFrame(trans: JsonNode, im: SpriteSheetImage): JsonNode =
    result = trans
    if result.isNil:
        result = %*[im.srcBounds.x, im.srcBounds.y, 0]
    else:
        result.elems[0] = %(result[0].getFNum() + im.srcBounds.x.float)
        result.elems[1] = %(result[1].getFNum() + im.srcBounds.y.float)

proc translationAnimFromFrameAnim(im: SpriteSheetImage, frameAnim: JsonNode, o: ImageOccurence): JsonNode =
    result = newJObject()
    let values = newJArray()
    let vl = frameAnim["values"].len

    for i in 0 ..< vl:
        values.add(%*[o.originalTranslationInNode.x, o.originalTranslationInNode.y, 0])

    result["values"] = values

    result["duration"] = frameAnim["duration"]
    result["frameLerp"] = %false
    let numLoops = frameAnim["numberOfLoops"]
    if not numLoops.isNil:
        result["numberOfLoops"] = numLoops

proc destPath(tool: ImgTool, origPath: string): string =
    let relPath = relativePathToPath(tool.originalResPath, origPath)
    result = tool.resPath / relPath

proc adjustImageNode(tool: ImgTool, im: SpriteSheetImage, o: ImageOccurence) =
    # Fixup the fileName node to contain spritesheet filename and texCoords
    let result = newJObject()
    o.parentComponent["fileNames"].elems[o.frameIndex] = result
    doAssert(not im.spriteSheet.isNil)
    result["file"] = %relativePathToPath(tool.destPath(o.compPath.parentDir()), tool.resPath / tool.outPrefix & $im.spriteSheet.index & ".png")
    let w = im.spriteSheet.packer.width.float
    let h = im.spriteSheet.packer.height.float
    result["tex"] = %*[(im.pos.x.float + 0.5) / w, (im.pos.y.float + 0.5) / h, ((im.pos.x + im.targetSize.width).float + 0.5) / w, ((im.pos.y + im.targetSize.height).float + 0.5) / h]
    result["size"] = %*[im.srcSize.width, im.srcSize.height]

    let jNode = o.parentNode

    echo "ADJUST IM NODE: ", im.originalPath

    if im.srcBounds.x > 0 or im.srcBounds.y > 0:
        # Node position has changed
        if o.frameIndex == 0:
            echo "Adjust ", im.originalPath
            echo im.srcBounds
            jNode["translation"] = adjustTranslationValueForFrame(jNode["translation"], im)
            echo "new trans: ", jNode["translation"]<commit_msg>Removed extra logging<commit_after>import nimPNG
import os, osproc, json, strutils, times, sequtils, tables, algorithm, math

import nimx.rect_packer
import nimx.types
import nimx.pathutils

import imgtools.imgtools

type Rect = imgtools.Rect
type Point = tuple[x, y: int32]
type Size = tuple[width, height: int]

const consumeLessMemory = defined(windows)

type
    ImageOccurence = object
        parentComposition, parentNode, parentComponent: JsonNode
        frameIndex: int
        compPath: string
        originalTranslationInNode: tuple[x, y: float]

    SpriteSheetImage = ref object
        spriteSheet: SpriteSheet # Index of sprite sheet in tool.images array
        srcBounds: Rect
        actualBounds: Rect
        originalPath: string
        srcSize: Size
        targetSize: Size
        pos: Point
        png: PNGResult
        occurences: seq[ImageOccurence]

    SpriteSheet = ref object
        index: int # Index of sprite sheet in tool.images array
        images: seq[SpriteSheetImage]
        packer: RectPacker

proc newSpriteSheetImage(path: string): SpriteSheetImage =
    result.new()
    result.originalPath = path
    result.occurences = @[]

proc newSpriteSheet(minSize: Size): SpriteSheet =
    result.new()
    let px = max(nextPowerOfTwo(minSize.width), 1024)
    let py = max(nextPowerOfTwo(minSize.height), 1024)
    result.packer = newPacker(px.int32, py.int32)
    result.packer.maxX = px.int32
    result.packer.maxY = py.int32
    result.images = newSeq[SpriteSheetImage]()

type ImgTool* = ref object
    compositionPaths*: seq[string]
    compositions: seq[JsonNode]
    outPrefix*: string #
    originalResPath*: string #
    resPath*: string #
    compressOutput*: bool
    images: Table[string, SpriteSheetImage]
    spriteSheets: seq[SpriteSheet]

    latestOriginalModificationDate: Time


proc newImgTool*(): ImgTool =
    result.new()
    result.images = initTable[string, SpriteSheetImage]()
    result.spriteSheets = newSeq[SpriteSheet]()

proc withSpriteNodes(n: JsonNode, p: proc(j, s: JsonNode)) =
    let components = n["components"]
    if not components.isNil:
        let sprite = components["Sprite"]
        if not sprite.isNil:
            p(n, sprite)
    let children = n["children"]
    if not children.isNil:
        for c in children:
            withSpriteNodes(c, p)

proc tryPackImage(ss: SpriteSheet, im: SpriteSheetImage): bool =
    im.pos = ss.packer.packAndGrow(im.targetSize.width.int32, im.targetSize.height.int32)
    result = im.pos.hasSpace
    if result:
        ss.images.add(im)
        im.spriteSheet = ss

proc composeAndWrite(tool: ImgTool, ss: SpriteSheet, path: string) =
    var data = newString(ss.packer.width * ss.packer.height * 4)
    for im in ss.images:
        if im.png.isNil:
            im.png = loadPNG32(im.originalPath)

        if im.srcSize == im.targetSize:
            blitImage(
                data, ss.packer.width, ss.packer.height, # Target image
                im.pos.x, im.pos.y, # Position in target image
                im.png.data, im.png.width, im.png.height,
                im.srcBounds.x, im.srcBounds.y, im.srcBounds.width, im.srcBounds.height)
        else:
            resizeImage(im.png.data, im.png.width, im.png.height,
                data, ss.packer.width, ss.packer.height,
                im.srcBounds.x, im.srcBounds.y, im.srcBounds.width, im.srcBounds.height,
                im.pos.x, im.pos.y, im.targetSize.width, im.targetSize.height)

        im.png = nil # We no longer need the data in memory

    discard savePNG32(path, data, ss.packer.width, ss.packer.height)
    if tool.compressOutput:
        discard execCmd("pngquant --force --speed 1 -o " & path & " " & path)

    when consumeLessMemory:
        GC_fullCollect()

proc adjustTranslationValueForFrame(trans: JsonNode, im: SpriteSheetImage): JsonNode =
    result = trans
    if result.isNil:
        result = %*[im.srcBounds.x, im.srcBounds.y, 0]
    else:
        result.elems[0] = %(result[0].getFNum() + im.srcBounds.x.float)
        result.elems[1] = %(result[1].getFNum() + im.srcBounds.y.float)

proc translationAnimFromFrameAnim(im: SpriteSheetImage, frameAnim: JsonNode, o: ImageOccurence): JsonNode =
    result = newJObject()
    let values = newJArray()
    let vl = frameAnim["values"].len

    for i in 0 ..< vl:
        values.add(%*[o.originalTranslationInNode.x, o.originalTranslationInNode.y, 0])

    result["values"] = values

    result["duration"] = frameAnim["duration"]
    result["frameLerp"] = %false
    let numLoops = frameAnim["numberOfLoops"]
    if not numLoops.isNil:
        result["numberOfLoops"] = numLoops

proc destPath(tool: ImgTool, origPath: string): string =
    let relPath = relativePathToPath(tool.originalResPath, origPath)
    result = tool.resPath / relPath

proc adjustImageNode(tool: ImgTool, im: SpriteSheetImage, o: ImageOccurence) =
    # Fixup the fileName node to contain spritesheet filename and texCoords
    let result = newJObject()
    o.parentComponent["fileNames"].elems[o.frameIndex] = result
    doAssert(not im.spriteSheet.isNil)
    result["file"] = %relativePathToPath(tool.destPath(o.compPath.parentDir()), tool.resPath / tool.outPrefix & $im.spriteSheet.index & ".png")
    let w = im.spriteSheet.packer.width.float
    let h = im.spriteSheet.packer.height.float
    result["tex"] = %*[(im.pos.x.float + 0.5) / w, (im.pos.y.float + 0.5) / h, ((im.pos.x + im.targetSize.width).float + 0.5) / w, ((im.pos.y + im.targetSize.height).float + 0.5) / h]
    result["size"] = %*[im.srcSize.width, im.srcSize.height]

    let jNode = o.parentNode

    if im.srcBounds.x > 0 or im.srcBounds.y > 0:
        # Node position has changed
        if o.frameIndex == 0:
            jNode["translation"] = adjustTranslationValueForFrame(jNode["translation"], im)
{'fraction_non_alphanumeric': 0.11611153487583693, 'fraction_numerical': 0.007797270955165692, 'mean_word_length': 2.943850267379679, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1838062', 'n_tokens_mistral': 3907, 'n_tokens_neox': 3806, 'n_words': 887}