Skip to content

Commit

Permalink
Develop (#103) Reduce resolution & handle of RGBA PNGs
Browse files Browse the repository at this point in the history
* Reduce resolution extension

* handling RGBA PNGs

* prepare for 1.7.2
  • Loading branch information
maciejmalecki authored Jun 10, 2023
1 parent d8645b6 commit 2974268
Show file tree
Hide file tree
Showing 11 changed files with 289 additions and 37 deletions.
4 changes: 4 additions & 0 deletions CHANGES.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
= Change log

1.7.2::
* `Image`: Reducting resolution in both X and Y axes added to the image preprocessor.
* `Image`: Handling of both indexed and RGBA PNG files.

1.7.1::
* `Image`: Flipping on X, Y or both axes added to image preprocessor.

Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ plugins {
allprojects {

group = "com.github.c64lib"
version = "1.7.1"
version = "1.7.2"

if (project.hasProperty(tagPropertyName)) {
version = project.property(tagPropertyName) ?: version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ 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.FlipImageUseCase
import com.github.c64lib.rbt.processors.image.usecase.ReadSourceImageUseCase
import com.github.c64lib.rbt.processors.image.usecase.ReduceResolutionUseCase
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
Expand Down Expand Up @@ -144,6 +145,7 @@ class RetroAssemblerPlugin : Plugin<Project> {
task.extendImageUseCase = ExtendImageUseCase()
task.splitImageUseCase = SplitImageUseCase()
task.flipImageUseCase = FlipImageUseCase()
task.reduceResolutionUseCase = ReduceResolutionUseCase()
}
val preprocess = project.tasks.create(TASK_PREPROCESS, Preprocess::class.java)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import com.github.c64lib.rbt.processors.image.usecase.FlipImageCommand
import com.github.c64lib.rbt.processors.image.usecase.FlipImageUseCase
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.ReduceResolutionCommand
import com.github.c64lib.rbt.processors.image.usecase.ReduceResolutionUseCase
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
Expand All @@ -42,6 +44,7 @@ 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.ImageFlipExtension
import com.github.c64lib.rbt.shared.gradle.dsl.ImageReduceResolutionExtension
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
Expand Down Expand Up @@ -73,6 +76,8 @@ open class ProcessImage : DefaultTask() {

@Internal lateinit var flipImageUseCase: FlipImageUseCase

@Internal lateinit var reduceResolutionUseCase: ReduceResolutionUseCase

@TaskAction
fun process() =
preprocessingExtension.imagePipelines.forEach { pipeline ->
Expand All @@ -93,7 +98,8 @@ open class ProcessImage : DefaultTask() {
when (extension) {
is ImageFlipExtension ->
listOf(
flipImageUseCase.apply(FlipImageCommand(image = it, axis = extension.axis)))
flipImageUseCase.apply(FlipImageCommand(image = it, axis = extension.axis)),
)
is ImageCutExtension ->
listOf(
cutImageUseCase.apply(
Expand All @@ -113,7 +119,9 @@ open class ProcessImage : DefaultTask() {
image = it,
newWidth = extension.newWidth ?: it.width,
newHeight = extension.newHeight ?: it.height,
fillColor = extension.fillColor)),
fillColor = extension.fillColor,
),
),
)
is ImageSplitExtension ->
splitImageUseCase
Expand All @@ -125,6 +133,16 @@ open class ProcessImage : DefaultTask() {
),
)
.toList()
is ImageReduceResolutionExtension ->
listOf(
reduceResolutionUseCase.apply(
ReduceResolutionCommand(
image = it,
reduceX = extension.reduceX,
reduceY = extension.reduceY,
),
),
)
else -> listOf(it)
}
}
Expand All @@ -134,6 +152,7 @@ open class ProcessImage : DefaultTask() {
extension.split?.let { process(postImages, it, useBuildDir) }
extension.extend?.let { process(postImages, it, useBuildDir) }
extension.flip?.let { process(postImages, it, useBuildDir) }
extension.reduceResolution?.let { process(postImages, it, useBuildDir) }

extension.spriteWriter?.let {
postImages.forEachIndexed { i, image ->
Expand All @@ -142,7 +161,8 @@ open class ProcessImage : DefaultTask() {
image,
WriteMethod.SPRITE,
toIndexedName(it.getOutput().get(), i, images),
useBuildDir),
useBuildDir,
),
)
}
}
Expand All @@ -154,7 +174,8 @@ open class ProcessImage : DefaultTask() {
image,
WriteMethod.BITMAP,
toIndexedName(it.getOutput().get(), i, images),
useBuildDir),
useBuildDir,
),
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,49 @@ class ReadPngImageAdapter : ReadImagePort {
override fun read(file: File): Image {
val pngReader = PngReader(file)
val imageInfo: ImageInfo = pngReader.imgInfo
val image = handlePNG(pngReader, imageInfo)
pngReader.end()

return image
}

private fun handlePNG(pngReader: PngReader, imageInfo: ImageInfo): Image =
if (imageInfo.channels < 3) {
handlePalettePNG(pngReader, imageInfo)
} else {
handleRgbaPNG(pngReader, imageInfo)
}

private fun handleRgbaPNG(pngReader: PngReader, imageInfo: ImageInfo): Image {
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 scanline = row.scanline
val triples = toTuples(scanline, 4)
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
}
}
return image
}

private fun handlePalettePNG(pngReader: PngReader, imageInfo: ImageInfo): Image {
val paletteChunks = pngReader.chunksList.getById(PngChunkPLTE.ID)
val paletteChunk = paletteChunks.first() as PngChunkPLTE
val width = imageInfo.cols
Expand All @@ -51,7 +94,7 @@ class ReadPngImageAdapter : ReadImagePort {
val color =
when (row) {
is ImageLineInt -> {
val triples = toTriples(ImageLineHelper.palette2rgb(row, paletteChunk, null))
val triples = toTuples(ImageLineHelper.palette2rgb(row, paletteChunk, null))
val r = triples[x][0]
val g = triples[x][1]
val b = triples[x][2]
Expand All @@ -65,21 +108,19 @@ class ReadPngImageAdapter : ReadImagePort {
image[x, y] = color
}
}
pngReader.end()

return image
}

private fun toTriples(array: IntArray): Array<IntArray> {
if (array.size % 3 != 0) {
throw IllegalArgumentException("Input array size must be a multiple of 3")
private fun toTuples(array: IntArray, size: Int = 3): Array<IntArray> {
if (array.size % size != 0) {
throw IllegalArgumentException("Input array size must be a multiple of $size")
}

val tripleArraySize = array.size / 3
val result = Array(tripleArraySize) { IntArray(3) }
val tripleArraySize = array.size / size
val result = Array(tripleArraySize) { IntArray(size) }

for (i in 0 until tripleArraySize) {
result[i] = intArrayOf(array[3 * i], array[3 * i + 1], array[3 * i + 2])
result[i] = array.sliceArray(i * size until (i + 1) * size)
}

return result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,34 +31,40 @@ import io.kotest.matchers.shouldNotBe
import java.io.File

class PngImageReaderTest :
BehaviorSpec({
val imageReader = ReadPngImageAdapter()
BehaviorSpec(
{
val imageReader = ReadPngImageAdapter()

Given("a PNG file from resources") {
val pngFile = File(javaClass.getResource("/test-png.png").file)
arrayOf("/test-png.png", "/test-png-2.png").forEach { fileName ->
Given("a PNG file from resources") {
val pngFile = File(javaClass.getResource(fileName).file)

When("the image is read using PngImageReader") {
val image = imageReader.read(pngFile)
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
}
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(0, 0), Pair(12, 0), Pair(14, 1), Pair(6, 16)).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)
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)
}
}
}
}
}
}
}
})
},
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
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 ReduceResolutionCommand(val image: Image, val reduceY: Int, val reduceX: Int)

class ReduceResolutionUseCase {
fun apply(command: ReduceResolutionCommand): Image {
require(command.reduceY > 0) { "reduceY must be greater than 0" }
require(command.reduceX > 0) { "reduceX must be greater than 0" }

val newWidth = command.image.width / command.reduceX
val newHeight = command.image.height / command.reduceY
val newImage = Image(newWidth, newHeight)

for (y in 0 until newHeight) {
for (x in 0 until newWidth) {
newImage[x, y] = command.image[x * command.reduceX, y * command.reduceY]
}
}

return newImage
}
}
Loading

0 comments on commit 2974268

Please sign in to comment.