From e242f75ae6d459fd15b26890f0bba7f7c7095e48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Ma=C5=82ecki?= Date: Sun, 14 May 2023 01:19:11 +0200 Subject: [PATCH] Adding image manipulation preprocessor --- build.gradle.kts | 2 +- .../src/main/kotlin/rbt.kotlin.gradle.kts | 1 + gradle.properties | 1 + infra/gradle/build.gradle.kts | 19 ++ .../c64lib/gradle/RetroAssemblerPlugin.kt | 22 ++- .../adapters/in/gradle/Goattracker.kt | 39 ++--- .../image/adapters/in/gradle/build.gradle.kts | 14 ++ .../image/adapters/in/gradle/ProcessImage.kt | 162 ++++++++++++++++++ .../image/adapters/out/file/build.gradle.kts | 11 ++ .../adapters/out/file/C64CharsetWriter.kt | 75 ++++++++ .../adapters/out/file/C64SpriteWriter.kt | 66 +++++++ .../image/adapters/out/file/WriterUtils.kt | 45 +++++ .../adapters/out/file/C64CharsetWriterTest.kt | 73 ++++++++ .../adapters/out/file/C64SpriteWriterTest.kt | 75 ++++++++ .../image/adapters/out/png/build.gradle.kts | 13 ++ .../adapters/out/png/ReadPngImageAdapter.kt | 87 ++++++++++ .../out/png/ReadPngImageAdapterTest.kt | 64 +++++++ .../out/png/src/test/resources/test-png.png | Bin 0 -> 611 bytes processors/image/build.gradle.kts | 9 + .../rbt/processors/image/domain/Image.kt | 99 +++++++++++ .../image/usecase/CutImageUseCase.kt | 40 +++++ .../image/usecase/CutSourceImageUseCase.kt | 63 +++++++ .../image/usecase/ExtendImageUseCase.kt | 40 +++++ .../image/usecase/ReadSourceImageUseCase.kt | 35 ++++ .../image/usecase/SplitImageUseCase.kt | 57 ++++++ .../image/usecase/WriteImageUseCase.kt | 55 ++++++ .../image/usecase/port/ReadImagePort.kt | 32 ++++ .../image/usecase/port/WriteCharsetPort.kt | 32 ++++ .../image/usecase/port/WriteSpritePort.kt | 32 ++++ .../rbt/processors/image/domain/ImageTest.kt | 105 ++++++++++++ .../usecase/CutSourceImageUseCaseTest.kt | 85 +++++++++ .../image/usecase/SplitImageUseCaseTest.kt | 84 +++++++++ settings.gradle.kts | 5 + .../github/c64lib/rbt/shared/domain/Color.kt | 27 +++ .../github/c64lib/rbt/shared/gradle/Tasks.kt | 2 + .../shared/gradle/dsl/ImageCutExtension.kt | 38 ++++ .../shared/gradle/dsl/ImageExtendExtension.kt | 38 ++++ .../gradle/dsl/ImagePipelineExtension.kt | 41 +++++ .../shared/gradle/dsl/ImageSplitExtension.kt | 35 ++++ .../dsl/ImageTransformationExtension.kt | 73 ++++++++ .../shared/gradle/dsl/ImageWriterExtension.kt | 36 ++++ .../gradle/dsl/PreprocessingExtension.kt | 8 + 42 files changed, 1818 insertions(+), 22 deletions(-) create mode 100644 processors/image/adapters/in/gradle/build.gradle.kts create mode 100644 processors/image/adapters/in/gradle/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/in/gradle/ProcessImage.kt create mode 100644 processors/image/adapters/out/file/build.gradle.kts create mode 100644 processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64CharsetWriter.kt create mode 100644 processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64SpriteWriter.kt create mode 100644 processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/WriterUtils.kt create mode 100644 processors/image/adapters/out/file/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64CharsetWriterTest.kt create mode 100644 processors/image/adapters/out/file/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64SpriteWriterTest.kt create mode 100644 processors/image/adapters/out/png/build.gradle.kts create mode 100644 processors/image/adapters/out/png/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/png/ReadPngImageAdapter.kt create mode 100644 processors/image/adapters/out/png/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/png/ReadPngImageAdapterTest.kt create mode 100644 processors/image/adapters/out/png/src/test/resources/test-png.png create mode 100644 processors/image/build.gradle.kts create mode 100644 processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/domain/Image.kt create mode 100644 processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutImageUseCase.kt create mode 100644 processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutSourceImageUseCase.kt create mode 100644 processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/ExtendImageUseCase.kt create mode 100644 processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/ReadSourceImageUseCase.kt create mode 100644 processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/SplitImageUseCase.kt create mode 100644 processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/WriteImageUseCase.kt create mode 100644 processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/ReadImagePort.kt create mode 100644 processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/WriteCharsetPort.kt create mode 100644 processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/WriteSpritePort.kt create mode 100644 processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/domain/ImageTest.kt create mode 100644 processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutSourceImageUseCaseTest.kt create mode 100644 processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/usecase/SplitImageUseCaseTest.kt create mode 100644 shared/domain/src/main/kotlin/com/github/c64lib/rbt/shared/domain/Color.kt create mode 100644 shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageCutExtension.kt create mode 100644 shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageExtendExtension.kt create mode 100644 shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImagePipelineExtension.kt create mode 100644 shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageSplitExtension.kt create mode 100644 shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageTransformationExtension.kt create mode 100644 shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageWriterExtension.kt diff --git a/build.gradle.kts b/build.gradle.kts index e2a4d2b3..b80f879a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ plugins { allprojects { group = "com.github.c64lib" - version = "1.6.0" + version = "1.7.0" if (project.hasProperty(tagPropertyName)) { version = project.property(tagPropertyName) ?: version diff --git a/buildSrc/src/main/kotlin/rbt.kotlin.gradle.kts b/buildSrc/src/main/kotlin/rbt.kotlin.gradle.kts index d4553864..b6fb4fbd 100644 --- a/buildSrc/src/main/kotlin/rbt.kotlin.gradle.kts +++ b/buildSrc/src/main/kotlin/rbt.kotlin.gradle.kts @@ -35,6 +35,7 @@ dependencies { implementation("io.vavr:vavr-kotlin:0.10.2") testImplementation("org.junit.jupiter:junit-jupiter:5.7.0") testImplementation("io.kotest:kotest-runner-junit5:4.5.0") + testImplementation("org.mockito:mockito-core:3.11.2") } tasks.withType { useJUnitPlatform() } diff --git a/gradle.properties b/gradle.properties index fb7bdafc..ca460ff3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,3 +4,4 @@ vavrVersion = 1.0.0-alpha-3 vavrKotlinVersion = 0.10.2 kotestVersion = 4.5.0 junitVersion = 5.7.0 +pngjVersion = 2.1.0 diff --git a/infra/gradle/build.gradle.kts b/infra/gradle/build.gradle.kts index fdef9463..7f6b16b2 100644 --- a/infra/gradle/build.gradle.kts +++ b/infra/gradle/build.gradle.kts @@ -2,8 +2,10 @@ val kotlinVersion: String by project val vavrVersion: String by project val vavrKotlinVersion: String by project val gradleDownloadTaskVersion: String by project +val pngjVersion: String by project val tagPropertyName = "tag" + plugins { id("rbt.kotlin") id("java-gradle-plugin") @@ -13,6 +15,15 @@ plugins { group = "com.github.c64lib.retro-assembler" +configurations { + // Create a new configuration that extends from 'implementation' + create("resolvableImplementation") { + extendsFrom(project.configurations.implementation.get()) + isCanBeResolved = true + } +} + + tasks { val copySubProjectClasses by register("copySubProjectClasses") { @@ -25,6 +36,8 @@ group = "com.github.c64lib.retro-assembler" .filter { it.moduleGroup.startsWith(project.group.toString()) } .flatMap { it.moduleArtifacts } .map { it.file.parentFile.parentFile } + + copy { from(localDependencies) into(buildDir) @@ -62,6 +75,7 @@ dependencies { implementation(gradleApi()) implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") implementation("io.vavr:vavr:$vavrVersion") + implementation("ar.com.hjg:pngj:$pngjVersion") compileOnly(project(":shared:domain")) compileOnly(project(":shared:gradle")) @@ -93,6 +107,11 @@ dependencies { compileOnly(project(":processors:charpad")) compileOnly(project(":processors:charpad:adapters:in:gradle")) + + compileOnly(project(":processors:image")) + compileOnly(project(":processors:image:adapters:in:gradle")) + compileOnly(project(":processors:image:adapters:out:png")) + compileOnly(project(":processors:image:adapters:out:file")) } publishing { repositories { maven { url = uri("../../../consuming/maven-repo") } } } diff --git a/infra/gradle/src/main/kotlin/com/github/c64lib/gradle/RetroAssemblerPlugin.kt b/infra/gradle/src/main/kotlin/com/github/c64lib/gradle/RetroAssemblerPlugin.kt index 521c9ee0..dd403b87 100644 --- a/infra/gradle/src/main/kotlin/com/github/c64lib/gradle/RetroAssemblerPlugin.kt +++ b/infra/gradle/src/main/kotlin/com/github/c64lib/gradle/RetroAssemblerPlugin.kt @@ -53,6 +53,15 @@ import com.github.c64lib.rbt.processors.charpad.adapters.`in`.gradle.Charpad import com.github.c64lib.rbt.processors.goattracker.adapters.`in`.gradle.Goattracker import com.github.c64lib.rbt.processors.goattracker.adapters.out.gradle.ExecuteGt2RelocAdapter import com.github.c64lib.rbt.processors.goattracker.usecase.PackSongUseCase +import com.github.c64lib.rbt.processors.image.adapters.`in`.gradle.ProcessImage +import com.github.c64lib.rbt.processors.image.adapters.out.file.C64CharsetWriter +import com.github.c64lib.rbt.processors.image.adapters.out.file.C64SpriteWriter +import com.github.c64lib.rbt.processors.image.adapters.out.png.ReadPngImageAdapter +import com.github.c64lib.rbt.processors.image.usecase.CutImageUseCase +import com.github.c64lib.rbt.processors.image.usecase.ExtendImageUseCase +import com.github.c64lib.rbt.processors.image.usecase.ReadSourceImageUseCase +import com.github.c64lib.rbt.processors.image.usecase.SplitImageUseCase +import com.github.c64lib.rbt.processors.image.usecase.WriteImageUseCase import com.github.c64lib.rbt.processors.spritepad.adapters.`in`.gradle.Spritepad import com.github.c64lib.rbt.shared.domain.SemVer import com.github.c64lib.rbt.shared.filedownload.FileDownloader @@ -63,6 +72,7 @@ import com.github.c64lib.rbt.shared.gradle.TASK_CHARPAD import com.github.c64lib.rbt.shared.gradle.TASK_CLEAN import com.github.c64lib.rbt.shared.gradle.TASK_DEPENDENCIES import com.github.c64lib.rbt.shared.gradle.TASK_GOATTRACKER +import com.github.c64lib.rbt.shared.gradle.TASK_IMAGE import com.github.c64lib.rbt.shared.gradle.TASK_PREPROCESS import com.github.c64lib.rbt.shared.gradle.TASK_RESOLVE_DEV_DEPENDENCIES import com.github.c64lib.rbt.shared.gradle.TASK_SPRITEPAD @@ -123,9 +133,19 @@ class RetroAssemblerPlugin : Plugin { task.preprocessingExtension = preprocessExtension task.packSongUseCase = PackSongUseCase(ExecuteGt2RelocAdapter(project)) } + val image = + project.tasks.create(TASK_IMAGE, ProcessImage::class.java) { task -> + task.preprocessingExtension = preprocessExtension + task.readSourceImageUseCase = ReadSourceImageUseCase(ReadPngImageAdapter()) + task.writeImageUseCase = + WriteImageUseCase(C64SpriteWriter(project), C64CharsetWriter(project)) + task.cutImageUseCase = CutImageUseCase() + task.extendImageUseCase = ExtendImageUseCase() + task.splitImageUseCase = SplitImageUseCase() + } val preprocess = project.tasks.create(TASK_PREPROCESS, Preprocess::class.java) - preprocess.dependsOn(charpad, spritepad, goattracker) + preprocess.dependsOn(charpad, spritepad, goattracker, image) // TODO Somehow, the ResolveDevDeps should give the settings. How!? val settings = diff --git a/processors/goattracker/adapters/in/gradle/src/main/kotlin/com/github/c64lib/rbt/processors/goattracker/adapters/in/gradle/Goattracker.kt b/processors/goattracker/adapters/in/gradle/src/main/kotlin/com/github/c64lib/rbt/processors/goattracker/adapters/in/gradle/Goattracker.kt index 52fb50fb..50507025 100644 --- a/processors/goattracker/adapters/in/gradle/src/main/kotlin/com/github/c64lib/rbt/processors/goattracker/adapters/in/gradle/Goattracker.kt +++ b/processors/goattracker/adapters/in/gradle/src/main/kotlin/com/github/c64lib/rbt/processors/goattracker/adapters/in/gradle/Goattracker.kt @@ -45,25 +45,24 @@ open class Goattracker : DefaultTask() { @Internal lateinit var packSongUseCase: PackSongUseCase @TaskAction - fun process() { - preprocessingExtension.goattrackerPipelines.forEach { pipeline -> - pipeline.outputs.forEach { output -> - packSongUseCase.apply( - PackSongCommand( - source = pipeline.getInput().get(), - output = output.getOutput().get(), - executable = output.executable, - useBuildDir = pipeline.getUseBuildDir().getOrElse(false), - bufferedSidWrites = output.bufferedSidWrites, - disableOptimization = output.disableOptimization, - playerMemoryLocation = output.playerMemoryLocation, - sfxSupport = output.sfxSupport, - sidMemoryLocation = output.sidMemoryLocation, - storeAuthorInfo = output.storeAuthorInfo, - volumeChangeSupport = output.volumeChangeSupport, - zeroPageLocation = output.zeroPageLocation, - zeropageGhostRegisters = output.zeropageGhostRegisters)) + fun process() = + preprocessingExtension.goattrackerPipelines.forEach { pipeline -> + pipeline.outputs.forEach { output -> + packSongUseCase.apply( + PackSongCommand( + source = pipeline.getInput().get(), + output = output.getOutput().get(), + executable = output.executable, + useBuildDir = pipeline.getUseBuildDir().getOrElse(false), + bufferedSidWrites = output.bufferedSidWrites, + disableOptimization = output.disableOptimization, + playerMemoryLocation = output.playerMemoryLocation, + sfxSupport = output.sfxSupport, + sidMemoryLocation = output.sidMemoryLocation, + storeAuthorInfo = output.storeAuthorInfo, + volumeChangeSupport = output.volumeChangeSupport, + zeroPageLocation = output.zeroPageLocation, + zeropageGhostRegisters = output.zeropageGhostRegisters)) + } } - } - } } diff --git a/processors/image/adapters/in/gradle/build.gradle.kts b/processors/image/adapters/in/gradle/build.gradle.kts new file mode 100644 index 00000000..076c22c1 --- /dev/null +++ b/processors/image/adapters/in/gradle/build.gradle.kts @@ -0,0 +1,14 @@ +plugins { + id("rbt.adapter.inbound.gradle") +} + +group = "com.github.c64lib.retro-assembler.processors.image.adapters.in" + +dependencies { + implementation(project(":processors:image")) + implementation(project(":shared:domain")) + implementation(project(":shared:gradle")) + implementation(project(":shared:binary-utils")) + implementation(project(":shared:processor")) + implementation(project(":shared:domain")) +} diff --git a/processors/image/adapters/in/gradle/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/in/gradle/ProcessImage.kt b/processors/image/adapters/in/gradle/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/in/gradle/ProcessImage.kt new file mode 100644 index 00000000..32cd8af0 --- /dev/null +++ b/processors/image/adapters/in/gradle/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/in/gradle/ProcessImage.kt @@ -0,0 +1,162 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.adapters.`in`.gradle + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.processors.image.usecase.CutImageCommand +import com.github.c64lib.rbt.processors.image.usecase.CutImageUseCase +import com.github.c64lib.rbt.processors.image.usecase.ExtendImageCommand +import com.github.c64lib.rbt.processors.image.usecase.ExtendImageUseCase +import com.github.c64lib.rbt.processors.image.usecase.ReadSourceImageCommand +import com.github.c64lib.rbt.processors.image.usecase.ReadSourceImageUseCase +import com.github.c64lib.rbt.processors.image.usecase.SplitImageCommand +import com.github.c64lib.rbt.processors.image.usecase.SplitImageUseCase +import com.github.c64lib.rbt.processors.image.usecase.WriteImageCommand +import com.github.c64lib.rbt.processors.image.usecase.WriteImageUseCase +import com.github.c64lib.rbt.processors.image.usecase.WriteMethod +import com.github.c64lib.rbt.shared.domain.Color +import com.github.c64lib.rbt.shared.gradle.GROUP_BUILD +import com.github.c64lib.rbt.shared.gradle.dsl.ImageCutExtension +import com.github.c64lib.rbt.shared.gradle.dsl.ImageExtendExtension +import com.github.c64lib.rbt.shared.gradle.dsl.ImageSplitExtension +import com.github.c64lib.rbt.shared.gradle.dsl.ImageTransformationExtension +import com.github.c64lib.rbt.shared.gradle.dsl.PreprocessingExtension +import java.io.File +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction + +open class ProcessImage : DefaultTask() { + + init { + description = "Processes PNG Image and convert it into hardware or software sprite shape(s)." + group = GROUP_BUILD + } + + @Input lateinit var preprocessingExtension: PreprocessingExtension + + @Internal lateinit var readSourceImageUseCase: ReadSourceImageUseCase + + @Internal lateinit var writeImageUseCase: WriteImageUseCase + + @Internal lateinit var cutImageUseCase: CutImageUseCase + + @Internal lateinit var extendImageUseCase: ExtendImageUseCase + + @Internal lateinit var splitImageUseCase: SplitImageUseCase + + @TaskAction + fun process() = + preprocessingExtension.imagePipelines.forEach { pipeline -> + val inputFile = requireNotNull(pipeline.getInput().get()) + val image = readSourceImageUseCase.apply(ReadSourceImageCommand(inputFile)) + process(arrayOf(image), pipeline, pipeline.getUseBuildDir().get() ?: true) + } + + private fun process( + images: Array, + extension: ImageTransformationExtension, + useBuildDir: Boolean + ) { + + val postImages: Array = + images + .flatMap { + when (extension) { + is ImageCutExtension -> + listOf( + cutImageUseCase.apply( + CutImageCommand( + image = it, + left = extension.left, + top = extension.top, + width = extension.width ?: (it.width - extension.left), + height = extension.height ?: (it.height - extension.top), + ), + ), + ) + is ImageExtendExtension -> + listOf( + extendImageUseCase.apply( + ExtendImageCommand( + image = it, + newWidth = extension.newWidth ?: it.width, + newHeight = extension.newHeight ?: it.height, + fillColor = extension.fillColor ?: Color(0, 0, 0, 255)), + ), + ) + is ImageSplitExtension -> + splitImageUseCase + .apply( + SplitImageCommand( + image = it, + subImageWidth = extension.width ?: it.width, + subImageHeight = extension.height ?: it.height, + ), + ) + .toList() + else -> listOf(it) + } + } + .toTypedArray() + + extension.cut?.let { process(postImages, it, useBuildDir) } + extension.split?.let { process(postImages, it, useBuildDir) } + extension.extend?.let { process(postImages, it, useBuildDir) } + + extension.spriteWriter?.let { + postImages.forEachIndexed { i, image -> + writeImageUseCase.apply( + WriteImageCommand( + image, + WriteMethod.SPRITE, + toIndexedName(it.getOutput().get(), i, images), + useBuildDir), + ) + } + } + + extension.bitmapWriter?.let { + postImages.forEachIndexed { i, image -> + writeImageUseCase.apply( + WriteImageCommand( + image, + WriteMethod.BITMAP, + toIndexedName(it.getOutput().get(), i, images), + useBuildDir), + ) + } + } + } + + private fun toIndexedName(file: File, index: Int, array: Array): File = + when (array.size) { + 1 -> file + 0 -> throw GradleException("No images to process") + else -> File(file.parent, "${file.nameWithoutExtension}_${index}.${file.extension}") + } +} diff --git a/processors/image/adapters/out/file/build.gradle.kts b/processors/image/adapters/out/file/build.gradle.kts new file mode 100644 index 00000000..aeaf9a2b --- /dev/null +++ b/processors/image/adapters/out/file/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("rbt.adapter.outbound.gradle") +} + +group = "com.github.c64lib.retro-assembler.processors.image" + +dependencies { + implementation(project(":processors:image")) + implementation(project(":shared:domain")) + implementation(project(":shared:gradle")) +} diff --git a/processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64CharsetWriter.kt b/processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64CharsetWriter.kt new file mode 100644 index 00000000..cda72491 --- /dev/null +++ b/processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64CharsetWriter.kt @@ -0,0 +1,75 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.adapters.out.file + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.processors.image.usecase.port.WriteCharsetPort +import com.github.c64lib.rbt.shared.domain.Color +import java.io.File +import java.io.FileOutputStream +import normalize +import org.gradle.api.Project + +class C64CharsetWriter(private val project: Project) : WriteCharsetPort { + + override fun write(image: Image, toFile: File, useBuildDir: Boolean) { + require(image.width % 8 == 0) { + "Image width must be a multiple of 8 for Commodore 64 hires charset" + } + require(image.height % 8 == 0) { + "Image height must be a multiple of 8 for Commodore 64 hires charset" + } + + val numBlocksX = image.width / 8 + val numBlocksY = image.height / 8 + val dataSize = numBlocksX * numBlocksY * 8 + val charsetData = ByteArray(dataSize) + + for (blockY in 0 until numBlocksY) { + for (blockX in 0 until numBlocksX) { + for (y in 0 until 8) { + val yOffset = (blockY * 8) * numBlocksX * 8 + val xOffset = blockX * 8 + + val byte = + (pixelValue(image[xOffset, yOffset + y]) shl 7) or + (pixelValue(image[xOffset + 1, yOffset + y]) shl 6) or + (pixelValue(image[xOffset + 2, yOffset + y]) shl 5) or + (pixelValue(image[xOffset + 3, yOffset + y]) shl 4) or + (pixelValue(image[xOffset + 4, yOffset + y]) shl 3) or + (pixelValue(image[xOffset + 5, yOffset + y]) shl 2) or + (pixelValue(image[xOffset + 6, yOffset + y]) shl 1) or + pixelValue(image[xOffset + 7, yOffset + y]) + + charsetData[blockY * numBlocksX * 8 + blockX * 8 + y] = byte.toByte() + } + } + } + + FileOutputStream(normalize(project, toFile, useBuildDir)).use { it.write(charsetData) } + } + + private fun pixelValue(color: Color) = if (color != Color(0, 0, 0, 255)) 1 else 0 +} diff --git a/processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64SpriteWriter.kt b/processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64SpriteWriter.kt new file mode 100644 index 00000000..f5095809 --- /dev/null +++ b/processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64SpriteWriter.kt @@ -0,0 +1,66 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.adapters.out.file + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.processors.image.usecase.port.WriteSpritePort +import com.github.c64lib.rbt.shared.domain.Color +import java.io.File +import java.io.FileOutputStream +import normalize +import org.gradle.api.Project + +class C64SpriteWriter( + private val project: Project, +) : WriteSpritePort { + override fun write(image: Image, toFile: File, useBuildDir: Boolean) { + require(image.width == 24) { "Sprite width must be 24 pixels for Commodore 64 hires sprites" } + require(image.height == 21) { "Sprite height must be 21 pixels for Commodore 64 hires sprites" } + + val spriteData = ByteArray(64) + + for (y in 0 until 21) { + for (x in 0 until 24 step 8) { + val byte = + (pixelValue(image[x, y]) shl 7) or + (pixelValue(image[x + 1, y]) shl 6) or + (pixelValue(image[x + 2, y]) shl 5) or + (pixelValue(image[x + 3, y]) shl 4) or + (pixelValue(image[x + 4, y]) shl 3) or + (pixelValue(image[x + 5, y]) shl 2) or + (pixelValue(image[x + 6, y]) shl 1) or + pixelValue(image[x + 7, y]) + + spriteData[y * 3 + x / 8] = byte.toByte() + } + } + + FileOutputStream(normalize(project, toFile, useBuildDir)).use { it.write(spriteData) } + } + + private fun pixelValue(color: Color): Int { + return if (color != Color(0, 0, 0, 255)) 1 else 0 + } +} diff --git a/processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/WriterUtils.kt b/processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/WriterUtils.kt new file mode 100644 index 00000000..86ca818b --- /dev/null +++ b/processors/image/adapters/out/file/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/WriterUtils.kt @@ -0,0 +1,45 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +import java.io.File +import org.gradle.api.Project + +internal fun normalize(project: Project, output: File?, outputToBuildDir: Boolean): File? = + if (output != null && outputToBuildDir) { + // TODO refactor! + + val outputRelativePath = + project.layout.projectDirectory.asFile.toPath().relativize(output.toPath()) + val resultPath = + project.layout.buildDirectory + .dir(project.buildDir.absolutePath) + .get() + .asFile + .toPath() + .resolve(outputRelativePath) + resultPath.parent?.toFile()?.mkdirs() + resultPath.toFile() + } else { + output + } diff --git a/processors/image/adapters/out/file/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64CharsetWriterTest.kt b/processors/image/adapters/out/file/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64CharsetWriterTest.kt new file mode 100644 index 00000000..a4e7f460 --- /dev/null +++ b/processors/image/adapters/out/file/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64CharsetWriterTest.kt @@ -0,0 +1,73 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.adapters.out.file + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.shared.domain.Color +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import java.io.File +import java.nio.file.Files +import org.gradle.api.Project +import org.mockito.Mockito + +class C64CharsetWriterTest : + BehaviorSpec({ + fun createTestImage(): Image { + val image = Image(16, 8) + + // Draw some example pixels + image[0, 0] = Color(255, 255, 255, 255) + image[7, 0] = Color(255, 255, 255, 255) + image[15, 7] = Color(255, 255, 255, 255) + + return image + } + + fun readCharsetData(file: File): ByteArray { + return Files.readAllBytes(file.toPath()) + } + + Given("a C64CharsetWriter instance") { + val project = Mockito.mock(Project::class.java) + val writer = C64CharsetWriter(project) + + When("writing a valid image to a file") { + val image = createTestImage() + val outputFile = File.createTempFile("charset_", ".bin") + outputFile.deleteOnExit() + + writer.write(image, outputFile, false) + + Then("the resulting file should have the correct size and data") { + val charsetData = readCharsetData(outputFile) + + charsetData.size shouldBe 16 + charsetData[0] shouldBe 0b10000001.toByte() + charsetData[15] shouldBe 0b00000001.toByte() + } + } + } + }) diff --git a/processors/image/adapters/out/file/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64SpriteWriterTest.kt b/processors/image/adapters/out/file/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64SpriteWriterTest.kt new file mode 100644 index 00000000..4c349036 --- /dev/null +++ b/processors/image/adapters/out/file/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/file/C64SpriteWriterTest.kt @@ -0,0 +1,75 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.adapters.out.file + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.shared.domain.Color +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import java.io.File +import java.nio.file.Files +import org.gradle.api.Project +import org.mockito.Mockito + +class C64SpriteWriterTest : + BehaviorSpec({ + fun createTestImage(): Image { + val image = Image(24, 21) + + // Draw some example pixels + image[0, 0] = Color(255, 255, 255, 255) + image[7, 0] = Color(255, 255, 255, 255) + image[10, 20] = Color(255, 255, 255, 255) + + return image + } + + fun readSpriteData(file: File): ByteArray { + return Files.readAllBytes(file.toPath()) + } + + Given("a C64SpriteWriter instance") { + val project = Mockito.mock(Project::class.java) + val writer = C64SpriteWriter(project) + + When("writing a valid image to a file") { + val image = createTestImage() + val outputFile = File.createTempFile("sprite_", ".bin") + outputFile.deleteOnExit() + + writer.write(image, outputFile, false) + + Then("the resulting file should have the correct size and data") { + val spriteData = readSpriteData(outputFile) + + spriteData.size shouldBe 64 + spriteData[0] shouldBe 0b10000001.toByte() + spriteData[61] shouldBe 0b00100000.toByte() + spriteData[62] shouldBe 0 + spriteData[63] shouldBe 0 + } + } + } + }) diff --git a/processors/image/adapters/out/png/build.gradle.kts b/processors/image/adapters/out/png/build.gradle.kts new file mode 100644 index 00000000..4bd82b48 --- /dev/null +++ b/processors/image/adapters/out/png/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + id("rbt.kotlin") +} + +val pngjVersion: String by project + +group = "com.github.c64lib.retro-assembler.processors.image" + +dependencies { + implementation(project(":processors:image")) + implementation(project(":shared:domain")) + implementation("ar.com.hjg:pngj:$pngjVersion") +} diff --git a/processors/image/adapters/out/png/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/png/ReadPngImageAdapter.kt b/processors/image/adapters/out/png/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/png/ReadPngImageAdapter.kt new file mode 100644 index 00000000..deeb049a --- /dev/null +++ b/processors/image/adapters/out/png/src/main/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/png/ReadPngImageAdapter.kt @@ -0,0 +1,87 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.adapters.out.png + +import ar.com.hjg.pngj.ImageInfo +import ar.com.hjg.pngj.ImageLineByte +import ar.com.hjg.pngj.ImageLineHelper +import ar.com.hjg.pngj.ImageLineInt +import ar.com.hjg.pngj.PngReader +import ar.com.hjg.pngj.chunks.PngChunkPLTE +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.processors.image.usecase.port.ReadImagePort +import com.github.c64lib.rbt.shared.domain.Color +import java.io.File + +class ReadPngImageAdapter : ReadImagePort { + override fun read(file: File): Image { + val pngReader = PngReader(file) + val imageInfo: ImageInfo = pngReader.imgInfo + val paletteChunks = pngReader.chunksList.getById(PngChunkPLTE.ID) + val paletteChunk = paletteChunks.first() as PngChunkPLTE + val width = imageInfo.cols + val height = imageInfo.rows + val image = Image(width, height) + + for (y in 0 until height) { + val row = pngReader.readRow(y) + for (x in 0 until width) { + val color = + when (row) { + is ImageLineInt -> { + val triples = toTriples(ImageLineHelper.palette2rgb(row, paletteChunk, null)) + val r = triples[x][0] + val g = triples[x][1] + val b = triples[x][2] + val a = 255 + Color(r, g, b, a) + } + is ImageLineByte -> + throw IllegalStateException("Unsupported row type: ${row.javaClass}") + else -> throw IllegalStateException("Unsupported row type: ${row.javaClass}") + } + image[x, y] = color + } + } + pngReader.end() + + return image + } + + private fun toTriples(array: IntArray): Array { + if (array.size % 3 != 0) { + throw IllegalArgumentException("Input array size must be a multiple of 3") + } + + val tripleArraySize = array.size / 3 + val result = Array(tripleArraySize) { IntArray(3) } + + for (i in 0 until tripleArraySize) { + result[i] = intArrayOf(array[3 * i], array[3 * i + 1], array[3 * i + 2]) + } + + return result + } +} diff --git a/processors/image/adapters/out/png/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/png/ReadPngImageAdapterTest.kt b/processors/image/adapters/out/png/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/png/ReadPngImageAdapterTest.kt new file mode 100644 index 00000000..57b55641 --- /dev/null +++ b/processors/image/adapters/out/png/src/test/kotlin/com/github/c64lib/rbt/processors/image/adapters/out/png/ReadPngImageAdapterTest.kt @@ -0,0 +1,64 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.adapters.out.png + +import com.github.c64lib.rbt.shared.domain.Color +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import java.io.File + +class PngImageReaderTest : + BehaviorSpec({ + val imageReader = ReadPngImageAdapter() + + Given("a PNG file from resources") { + val pngFile = File(javaClass.getResource("/test-png.png").file) + + When("the image is read using PngImageReader") { + val image = imageReader.read(pngFile) + + Then("the returned Image object should not be null and have valid dimensions") { + image shouldNotBe null + image.width shouldBe 32 * 4 + image.height shouldBe 32 + } + + listOf(Pair(0, 0), Pair(12, 0), Pair(11, 2), Pair(12, 2)).forEach { + Then( + "the returned Image object should have proper transparent pixel at [${it.first}, ${it.second}]") { + image[it.first, it.second] shouldBe Color(0, 0, 0, 255) + } + } + + listOf(Pair(10, 0), Pair(11, 0), Pair(9, 1), Pair(7, 2)).forEach { + Then( + "the returned Image object should have proper set pixel at [${it.first}, ${it.second}]") { + image[it.first, it.second] shouldBe Color(255, 255, 255, 255) + } + } + } + } + }) diff --git a/processors/image/adapters/out/png/src/test/resources/test-png.png b/processors/image/adapters/out/png/src/test/resources/test-png.png new file mode 100644 index 0000000000000000000000000000000000000000..f00e9b6ed04b20f3ddf02e7abe59832dc5ba8ced GIT binary patch literal 611 zcmeAS@N?(olHy`uVBq!ia0vp^4M42G!3-qd{%H^gQjEnx?oJHr&dIz4a*_jlLR^8g zl7fk=hE1BKVSkwQ{X(^y3)0>lp7r~a+03T_S^Eskr{sBO>IZqN7};3b+m{$5G)3n< zOrHHe{_K;}xBvYA|9|${?JaffnORwpVL@e1U2BYtZiXjcuin)^-K#6#0BHWt%A1FP zlwwJcUoZnu176_v#cv7FosFI@jv*1POQ&V?9ai9J-s$qL{;H>Lr9qPPm47pqv0BGF zEs$20+x+q6&vpNwDSWm0v+AXkU`Q38MA4o$DUZDjHB0hEbRMz=t!J42>dd#K*2YWO z2NrGlb$Maqwbe|A>$lpNPWZ)r;OE;@4_wYO#0IZfZ>92;so-7iyA8QNS2kYZK9FU> ze|uJ(1kk7$uS)N|MnC4a`mSeu{#?0k&wHL#`xq~<+N}#?u&Pfx@O9IHqkDELg|yDD z>E=4{=)%3gb~YD%t}j~Y9Q*TDG@h5gn(T1h>G#800c{r%#!24q&M=7AvAlSGXOnL^ z+s%Z3{mm15zgIP~EOOc`q3rQuO2dIo?Ej;Mv>zz0k!CTE6m(&Cs}8++f=#i>af zT2((kOy`+%X{Pj= width) { "New width must be greater or equal to the current width" } + require(newHeight >= height) { "New height must be greater or equal to the current height" } + + val newImage = Image(newWidth, newHeight) + + // Copy original image pixels + for (y in 0 until height) { + for (x in 0 until width) { + newImage[x, y] = this[x, y] + } + } + + // Fill the extended area with the specified color + for (y in 0 until newHeight) { + for (x in 0 until newWidth) { + if (x >= width || y >= height) { + newImage[x, y] = fillColor + } + } + } + + return newImage + } + + fun dump() { + for (y in 0 until height) { + for (x in 0 until width) { + print( + if (pixels[y][x] == Color(0, 0, 0, 255)) { + "." + } else { + "#" + }, + ) + } + println() + } + } +} diff --git a/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutImageUseCase.kt b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutImageUseCase.kt new file mode 100644 index 00000000..207609ce --- /dev/null +++ b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutImageUseCase.kt @@ -0,0 +1,40 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase + +import com.github.c64lib.rbt.processors.image.domain.Image + +data class CutImageCommand( + val image: Image, + val left: Int, + val top: Int, + val width: Int, + val height: Int +) + +class CutImageUseCase { + fun apply(command: CutImageCommand): Image = + command.image.subImage(command.top, command.left, command.width, command.height) +} diff --git a/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutSourceImageUseCase.kt b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutSourceImageUseCase.kt new file mode 100644 index 00000000..78eaee4d --- /dev/null +++ b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutSourceImageUseCase.kt @@ -0,0 +1,63 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.processors.image.usecase.port.ReadImagePort +import java.io.File + +data class CutSourceImageCommand( + val file: File, + val width: Int, + val height: Int, + val left: Int = 0, + val top: Int = 0 +) + +class CutSourceImageUseCase(private val readImagePort: ReadImagePort) { + + fun apply(command: CutSourceImageCommand): Array { + val sourceImage = readImagePort.read(command.file) + + require(command.width > 0) { "Subimage width must be greater than 0" } + require(command.height > 0) { "Subimage height must be greater than 0" } + require(command.width + command.left <= sourceImage.width) { + "Subimage width must be less than or equal to the image width" + } + require(command.height + command.top <= sourceImage.height) { + "Subimage height must be less than or equal to the image height" + } + + val subImages = mutableListOf() + var x = command.left + + while (x + command.width <= sourceImage.width) { + subImages.add(sourceImage.subImage(command.top, x, command.width, command.height)) + x += command.width + } + + return subImages.toTypedArray() + } +} diff --git a/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/ExtendImageUseCase.kt b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/ExtendImageUseCase.kt new file mode 100644 index 00000000..0e7de882 --- /dev/null +++ b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/ExtendImageUseCase.kt @@ -0,0 +1,40 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.shared.domain.Color + +data class ExtendImageCommand( + val image: Image, + val newWidth: Int, + val newHeight: Int, + val fillColor: Color = Color(0, 0, 0, 0) +) + +class ExtendImageUseCase { + fun apply(command: ExtendImageCommand): Image = + command.image.extend(command.newWidth, command.newHeight, command.fillColor) +} diff --git a/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/ReadSourceImageUseCase.kt b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/ReadSourceImageUseCase.kt new file mode 100644 index 00000000..0024a72f --- /dev/null +++ b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/ReadSourceImageUseCase.kt @@ -0,0 +1,35 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.processors.image.usecase.port.ReadImagePort +import java.io.File + +data class ReadSourceImageCommand(val file: File) + +class ReadSourceImageUseCase(private val readImagePort: ReadImagePort) { + fun apply(command: ReadSourceImageCommand): Image = readImagePort.read(command.file) +} diff --git a/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/SplitImageUseCase.kt b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/SplitImageUseCase.kt new file mode 100644 index 00000000..e02027c3 --- /dev/null +++ b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/SplitImageUseCase.kt @@ -0,0 +1,57 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase + +import com.github.c64lib.rbt.processors.image.domain.Image + +data class SplitImageCommand(val image: Image, val subImageWidth: Int, val subImageHeight: Int) + +class SplitImageUseCase { + fun apply(command: SplitImageCommand): Array { + require(command.subImageWidth > 0) { "Subimage width must be greater than 0" } + require(command.subImageHeight > 0) { "Subimage height must be greater than 0" } + require(command.image.width % command.subImageWidth == 0) { + "Image width must be a multiple of subimage width" + } + require(command.image.height % command.subImageHeight == 0) { + "Image height must be a multiple of subimage height" + } + + val numRows = command.image.height / command.subImageHeight + val numCols = command.image.width / command.subImageWidth + val subImages = mutableListOf() + + for (row in 0 until numRows) { + for (col in 0 until numCols) { + val left = col * command.subImageWidth + val top = row * command.subImageHeight + subImages.add( + command.image.subImage(top, left, command.subImageWidth, command.subImageHeight)) + } + } + + return subImages.toTypedArray() + } +} diff --git a/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/WriteImageUseCase.kt b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/WriteImageUseCase.kt new file mode 100644 index 00000000..8578f598 --- /dev/null +++ b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/WriteImageUseCase.kt @@ -0,0 +1,55 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.processors.image.usecase.port.WriteCharsetPort +import com.github.c64lib.rbt.processors.image.usecase.port.WriteSpritePort +import java.io.File + +enum class WriteMethod { + SPRITE, + BITMAP +} + +data class WriteImageCommand( + val image: Image, + val method: WriteMethod, + val output: File, + val useBuildDir: Boolean +) + +class WriteImageUseCase( + private val writeSpritePort: WriteSpritePort, + private val writeCharsetPort: WriteCharsetPort +) { + fun apply(command: WriteImageCommand) = + when (command.method) { + WriteMethod.SPRITE -> + writeSpritePort.write(command.image, command.output, command.useBuildDir) + WriteMethod.BITMAP -> + writeCharsetPort.write(command.image, command.output, command.useBuildDir) + } +} diff --git a/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/ReadImagePort.kt b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/ReadImagePort.kt new file mode 100644 index 00000000..c575bb32 --- /dev/null +++ b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/ReadImagePort.kt @@ -0,0 +1,32 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase.port + +import com.github.c64lib.rbt.processors.image.domain.Image +import java.io.File + +interface ReadImagePort { + fun read(file: File): Image +} diff --git a/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/WriteCharsetPort.kt b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/WriteCharsetPort.kt new file mode 100644 index 00000000..84f51875 --- /dev/null +++ b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/WriteCharsetPort.kt @@ -0,0 +1,32 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase.port + +import com.github.c64lib.rbt.processors.image.domain.Image +import java.io.File + +interface WriteCharsetPort { + fun write(image: Image, toFile: File, useBuildDir: Boolean) +} diff --git a/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/WriteSpritePort.kt b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/WriteSpritePort.kt new file mode 100644 index 00000000..7966c590 --- /dev/null +++ b/processors/image/src/main/kotlin/com/github/c64lib/rbt/processors/image/usecase/port/WriteSpritePort.kt @@ -0,0 +1,32 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase.port + +import com.github.c64lib.rbt.processors.image.domain.Image +import java.io.File + +interface WriteSpritePort { + fun write(image: Image, toFile: File, useBuildDir: Boolean) +} diff --git a/processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/domain/ImageTest.kt b/processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/domain/ImageTest.kt new file mode 100644 index 00000000..5df5560e --- /dev/null +++ b/processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/domain/ImageTest.kt @@ -0,0 +1,105 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.domain + +import com.github.c64lib.rbt.shared.domain.Color +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.ints.shouldBeExactly +import io.kotest.matchers.shouldBe + +class ImageTest : + BehaviorSpec({ + Given("An Image instance with dimensions 100x50") { + val image = Image(100, 50) + + When("checking dimensions") { + Then("the width should be 100 and the height should be 50") { + image.width shouldBeExactly 100 + image.height shouldBeExactly 50 + } + } + + When("setting and getting pixel colors") { + val color = Color(100, 200, 150, 255) + image[10, 20] = color + + Then("the color should be set and retrieved correctly") { image[10, 20] shouldBe color } + } + + When("creating a subImage") { + // Fill the image with unique colors for demonstration purposes + for (y in 0 until image.height) { + for (x in 0 until image.width) { + image[x, y] = Color(x % 256, y % 256, (x + y) % 256, 255) + } + } + + val subImage = image.subImage(10, 20, 30, 15) + + Then("the subImage should have the correct dimensions") { + subImage.width shouldBeExactly 30 + subImage.height shouldBeExactly 15 + } + + Then("the subImage should contain the correct region from the original image") { + for (y in 0 until subImage.height) { + for (x in 0 until subImage.width) { + subImage[x, y] shouldBe image[x + 20, y + 10] + } + } + } + } + + When("extending the image") { + // Fill the image with unique colors for demonstration purposes + for (y in 0 until image.height) { + for (x in 0 until image.width) { + image[x, y] = Color(x % 256, y % 256, (x + y) % 256, 255) + } + } + + val fillColor = Color(0, 0, 0, 255) + val extendedImage = image.extend(150, 80, fillColor) + + Then("the extended image should have the correct dimensions") { + extendedImage.width shouldBeExactly 150 + extendedImage.height shouldBeExactly 80 + } + + Then( + "the extended image should contain the original image and the fill color in the extended area") { + for (y in 0 until extendedImage.height) { + for (x in 0 until extendedImage.width) { + if (x < image.width && y < image.height) { + extendedImage[x, y] shouldBe image[x, y] + } else { + extendedImage[x, y] shouldBe fillColor + } + } + } + } + } + } + }) diff --git a/processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutSourceImageUseCaseTest.kt b/processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutSourceImageUseCaseTest.kt new file mode 100644 index 00000000..1500c3e1 --- /dev/null +++ b/processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/usecase/CutSourceImageUseCaseTest.kt @@ -0,0 +1,85 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.processors.image.usecase.port.ReadImagePort +import com.github.c64lib.rbt.shared.domain.Color +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe +import java.io.File + +class CutSourceImageUseCaseTest : + BehaviorSpec({ + Given("A CutImageUseCase with a ReadImagePort implementation") { + val readImagePort = + object : ReadImagePort { + override fun read(file: File): Image { + return Image(100, 50).apply { + for (y in 0 until height) { + for (x in 0 until width) { + this[x, y] = Color(x % 256, y % 256, (x + y) % 256, 255) + } + } + } + } + } + val cutSourceImageUseCase = CutSourceImageUseCase(readImagePort) + + When("a CutImageCommand with valid parameters is provided") { + val command = CutSourceImageCommand(File("dummy_file"), 20, 50) + val subImages = cutSourceImageUseCase.apply(command) + + Then("it should return an array of Image objects with the correct dimensions") { + subImages shouldHaveSize 5 + subImages.forEach { subImage -> + subImage.width shouldBe 20 + subImage.height shouldBe 50 + } + } + + Then("the subimages should contain the correct region from the original image") { + val originalImage = readImagePort.read(File("dummy_file")) + + subImages.forEachIndexed { index, subImage -> + for (y in 0 until subImage.height) { + for (x in 0 until subImage.width) { + subImage[x, y] shouldBe originalImage[x + 20 * index, y] + } + } + } + } + } + + When("a CutImageCommand with invalid parameters is provided") { + Then("it should throw an exception") { + val invalidCommand = CutSourceImageCommand(File("dummy_file"), 0, 50) + shouldThrow { cutSourceImageUseCase.apply(invalidCommand) } + } + } + } + }) diff --git a/processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/usecase/SplitImageUseCaseTest.kt b/processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/usecase/SplitImageUseCaseTest.kt new file mode 100644 index 00000000..6476a16e --- /dev/null +++ b/processors/image/src/test/kotlin/com/github/c64lib/rbt/processors/image/usecase/SplitImageUseCaseTest.kt @@ -0,0 +1,84 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.processors.image.usecase + +import com.github.c64lib.rbt.processors.image.domain.Image +import com.github.c64lib.rbt.shared.domain.Color +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.shouldBe + +class SplitImageUseCaseTest : + BehaviorSpec({ + Given("A CutImageUseCase instance") { + val splitImageUseCase = SplitImageUseCase() + + And("An Image instance with dimensions 100x50") { + val image = + Image(100, 50).apply { + for (y in 0 until height) { + for (x in 0 until width) { + this[x, y] = Color(x % 256, y % 256, (x + y) % 256, 255) + } + } + } + + When("a CutImageCommand with valid parameters is provided") { + val command = SplitImageCommand(image, 20, 25) + val subImages = splitImageUseCase.apply(command) + + Then("it should return an array of Image objects with the correct dimensions") { + subImages shouldHaveSize 10 + subImages.forEach { subImage -> + subImage.width shouldBe 20 + subImage.height shouldBe 25 + } + } + + Then("the subimages should contain the correct region from the original image") { + for (i in subImages.indices) { + val subImage = subImages[i] + val offsetX = (i % 5) * 20 + val offsetY = (i / 5) * 25 + + for (y in 0 until subImage.height) { + for (x in 0 until subImage.width) { + subImage[x, y] shouldBe image[x + offsetX, y + offsetY] + } + } + } + } + } + + When("a CutImageCommand with invalid parameters is provided") { + Then("it should throw an exception") { + val invalidCommand = SplitImageCommand(image, 0, 50) + shouldThrow { splitImageUseCase.apply(invalidCommand) } + } + } + } + } + }) diff --git a/settings.gradle.kts b/settings.gradle.kts index 4344d0c1..303fcdf5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,4 +34,9 @@ include(":processors:spritepad:adapters:in:gradle") include(":processors:charpad") include(":processors:charpad:adapters:in:gradle") +include(":processors:image") +include(":processors:image:adapters:in:gradle") +include(":processors:image:adapters:out:png") +include(":processors:image:adapters:out:file") + include(":doc") diff --git a/shared/domain/src/main/kotlin/com/github/c64lib/rbt/shared/domain/Color.kt b/shared/domain/src/main/kotlin/com/github/c64lib/rbt/shared/domain/Color.kt new file mode 100644 index 00000000..8a29214d --- /dev/null +++ b/shared/domain/src/main/kotlin/com/github/c64lib/rbt/shared/domain/Color.kt @@ -0,0 +1,27 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.shared.domain + +data class Color(val red: Int, val green: Int, val blue: Int, val alpha: Int) diff --git a/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/Tasks.kt b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/Tasks.kt index 3f1b37c1..bec0749e 100644 --- a/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/Tasks.kt +++ b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/Tasks.kt @@ -45,4 +45,6 @@ const val TASK_SPRITEPAD = "spritepad" const val TASK_GOATTRACKER = "goattracker" +const val TASK_IMAGE = "image" + const val TASK_PREPROCESS = "preprocess" diff --git a/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageCutExtension.kt b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageCutExtension.kt new file mode 100644 index 00000000..7e0f2bab --- /dev/null +++ b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageCutExtension.kt @@ -0,0 +1,38 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.shared.gradle.dsl + +import javax.inject.Inject +import org.gradle.api.model.ObjectFactory + +abstract class ImageCutExtension : ImageTransformationExtension { + + @Inject constructor(objectFactory: ObjectFactory) : super(objectFactory) + + var left: Int = 0 + var top: Int = 0 + var width: Int? = null + var height: Int? = null +} diff --git a/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageExtendExtension.kt b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageExtendExtension.kt new file mode 100644 index 00000000..77ea11a9 --- /dev/null +++ b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageExtendExtension.kt @@ -0,0 +1,38 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.shared.gradle.dsl + +import com.github.c64lib.rbt.shared.domain.Color +import javax.inject.Inject +import org.gradle.api.model.ObjectFactory + +abstract class ImageExtendExtension : ImageTransformationExtension { + + @Inject constructor(objectFactory: ObjectFactory) : super(objectFactory) + + var newWidth: Int? = null + var newHeight: Int? = null + var fillColor: Color = Color(0, 0, 0, 255) +} diff --git a/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImagePipelineExtension.kt b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImagePipelineExtension.kt new file mode 100644 index 00000000..7cc96a16 --- /dev/null +++ b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImagePipelineExtension.kt @@ -0,0 +1,41 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.shared.gradle.dsl + +import java.io.File +import javax.inject.Inject +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles + +abstract class ImagePipelineExtension : ImageTransformationExtension { + + @Inject constructor(objectFactory: ObjectFactory) : super(objectFactory) + + @InputFiles abstract fun getInput(): Property + + @Input abstract fun getUseBuildDir(): Property +} diff --git a/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageSplitExtension.kt b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageSplitExtension.kt new file mode 100644 index 00000000..ecf92cab --- /dev/null +++ b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageSplitExtension.kt @@ -0,0 +1,35 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.shared.gradle.dsl + +import javax.inject.Inject +import org.gradle.api.model.ObjectFactory + +abstract class ImageSplitExtension : ImageTransformationExtension { + @Inject constructor(objectFactory: ObjectFactory) : super(objectFactory) + + var width: Int? = null + var height: Int? = null +} diff --git a/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageTransformationExtension.kt b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageTransformationExtension.kt new file mode 100644 index 00000000..37096ea8 --- /dev/null +++ b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageTransformationExtension.kt @@ -0,0 +1,73 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.shared.gradle.dsl + +import javax.inject.Inject +import org.gradle.api.Action +import org.gradle.api.GradleException +import org.gradle.api.model.ObjectFactory + +abstract class ImageTransformationExtension +@Inject +constructor(protected val objectFactory: ObjectFactory) { + var spriteWriter: ImageWriterExtension? = null + + var bitmapWriter: ImageWriterExtension? = null + + var cut: ImageCutExtension? = null + + var split: ImageSplitExtension? = null + + var extend: ImageExtendExtension? = null + + fun sprite(action: Action) { + spriteWriter = execute(spriteWriter, action, ImageWriterExtension::class.java) + } + + fun bitmap(action: Action) { + bitmapWriter = execute(bitmapWriter, action, ImageWriterExtension::class.java) + } + + fun cut(action: Action) { + cut = execute(cut, action, ImageCutExtension::class.java) + } + + fun split(action: Action) { + split = execute(split, action, ImageSplitExtension::class.java) + } + + fun extend(action: Action) { + extend = execute(extend, action, ImageExtendExtension::class.java) + } + + private fun execute(value: T?, action: Action, clazz: Class): T { + if (value != null) { + throw GradleException("This action can be called only once (${clazz.name}).") + } + val ex = requireNotNull(objectFactory.newInstance(clazz)) + action.execute(ex) + return ex + } +} diff --git a/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageWriterExtension.kt b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageWriterExtension.kt new file mode 100644 index 00000000..f515da21 --- /dev/null +++ b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/ImageWriterExtension.kt @@ -0,0 +1,36 @@ +/* +MIT License + +Copyright (c) 2018-2023 c64lib: The Ultimate Commodore 64 Library +Copyright (c) 2018-2023 Maciej Małecki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package com.github.c64lib.rbt.shared.gradle.dsl + +import java.io.File +import javax.inject.Inject +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.tasks.OutputFile + +abstract class ImageWriterExtension @Inject constructor(private val objectFactory: ObjectFactory) { + + @OutputFile abstract fun getOutput(): Property +} diff --git a/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/PreprocessingExtension.kt b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/PreprocessingExtension.kt index 2e053369..f6d26537 100644 --- a/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/PreprocessingExtension.kt +++ b/shared/gradle/src/main/kotlin/com/github/c64lib/rbt/shared/gradle/dsl/PreprocessingExtension.kt @@ -40,6 +40,8 @@ constructor(private val objectFactory: ObjectFactory) { val goattrackerPipelines = ArrayList() + val imagePipelines = ArrayList() + fun charpad(action: Action) { val ex = objectFactory.newInstance(CharpadPipelineExtension::class.java) action.execute(ex) @@ -57,4 +59,10 @@ constructor(private val objectFactory: ObjectFactory) { action.execute(ex) goattrackerPipelines.add(ex) } + + fun image(action: Action) { + val ex = objectFactory.newInstance(ImagePipelineExtension::class.java) + action.execute(ex) + imagePipelines.add(ex) + } }