Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
iamgio committed Dec 3, 2023
2 parents 3df72fc + 6bc03d9 commit 9ee3e51
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 29 deletions.
1 change: 1 addition & 0 deletions imagelib/src/main/kotlin/pikt/error/ImageValueType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ object ImageValueType {

const val IMAGE = "image"
const val WRITABLE_IMAGE = "writable image"
const val COLOR = "color"
}
37 changes: 37 additions & 0 deletions imagelib/src/main/kotlin/pikt/imagelib/AwtImage.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package pikt.imagelib

import pikt.error.PiktIOException
import pikt.error.PiktIndexOutOfBoundsException
import java.awt.image.BufferedImage
import java.io.File
import java.io.IOException
import javax.imageio.ImageIO

private typealias AwtColor = java.awt.Color

/**
* [Image] implementation based on AWT [BufferedImage] for the JVM.
*
Expand All @@ -19,6 +22,37 @@ class AwtImage(private val image: BufferedImage) : WritableImage {
override val height: Int
get() = image.height

private fun checkCoordinates(x: Int, y: Int, reference: Any) {
if (x < 0 || x >= image.width) {
throw PiktIndexOutOfBoundsException(
index = x,
size = image.width,
reference
)
}

if (y < 0 || y >= image.height) {
throw PiktIndexOutOfBoundsException(
index = y,
size = image.height,
reference
)
}
}

override fun getColor(x: Int, y: Int): Color {
this.checkCoordinates(x, y, reference = object {})

val rgb = image.getRGB(x, y)
return AwtColor(rgb).toPiktColor()
}

override fun setColor(x: Int, y: Int, color: Color) {
this.checkCoordinates(x, y, reference = object {})

image.setRGB(x, y, color.toAwtColor().rgb)
}

override fun save(file: File) {
try {
ImageIO.write(image, file.extension, file)
Expand All @@ -29,6 +63,9 @@ class AwtImage(private val image: BufferedImage) : WritableImage {

override fun toString() = "AwtImage (width=$width, height=$height)"

private fun AwtColor.toPiktColor() = Color(red, green, blue)
private fun Color.toAwtColor() = AwtColor(red, green, blue)

companion object : ImageFactory<AwtImage> {

override fun blank(width: Int, height: Int, transparent: Boolean): AwtImage {
Expand Down
60 changes: 60 additions & 0 deletions imagelib/src/main/kotlin/pikt/imagelib/Color.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package pikt.imagelib

import pikt.error.PiktException
import pikt.error.PiktWrongArgumentTypeException
import pikt.error.ValueType.NUMBER
import pikt.stdlib.Struct

// TODO find a better way via color schemes instead of hardcoding colors
private const val RED = "`E70000`"
private const val GREEN = "`00E700`"
private const val BLUE = "`0000E7`"

private const val UPPER_LIMIT = 255

/**
* Representation of an RGB color.
*
* @param red the red component in 0-255 value
* @param green the green component in 0-255 value
* @param blue the blue component in 0-255 value
*/
class Color(red: Int = 0, green: Int = 0, blue: Int = 0) : Struct(RED to red, GREEN to green, BLUE to blue) {

/**
* The red component in 0-255 value
*/
val red: Int
get() = this[RED] as Int

/**
* The green component in 0-255 value
*/
val green: Int
get() = this[GREEN] as Int

/**
* The blue component in 0-255 value
*/
val blue: Int
get() = this[BLUE] as Int

override operator fun set(property: Any, value: Any) {
if (value !is Int) {
throw PiktWrongArgumentTypeException(
parameterName = "R/G/B",
argumentValue = value,
expectedType = NUMBER,
reference = object {}
)
}

if (value < 0 || value > UPPER_LIMIT) {
throw PiktException("0-255 value expected for R/G/B.", reference = object {})
}

super.set(property, value)
}

override fun toString() = "Color (red=$red, green=$green, blue=$blue)"
}
10 changes: 10 additions & 0 deletions imagelib/src/main/kotlin/pikt/imagelib/Image.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package pikt.imagelib

import pikt.error.PiktIndexOutOfBoundsException

/**
* Abstraction of images and their manipulations.
*/
Expand All @@ -14,4 +16,12 @@ interface Image {
* Height of the image, in pixels.
*/
val height: Int

/**
* @param x X coordinate
* @param y Y coordinate
* @return the color of the image at the given coordinate
* @throws PiktIndexOutOfBoundsException if one of the coordinates is negative or greater than the image size
*/
fun getColor(x: Int, y: Int): Color
}
102 changes: 76 additions & 26 deletions imagelib/src/main/kotlin/pikt/imagelib/ImageFunctions.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package pikt.imagelib

import pikt.error.ImageValueType.COLOR
import pikt.error.ImageValueType.IMAGE
import pikt.error.ImageValueType.WRITABLE_IMAGE
import pikt.error.PiktIOException
import pikt.error.PiktIndexOutOfBoundsException
import pikt.error.PiktWrongArgumentTypeException
import pikt.error.ValueType.NUMBER
import pikt.stdlib.newFile
Expand All @@ -11,6 +13,42 @@ import java.io.File
// Top-level functions to access image methods from Pikt.
// The only supported implementation is currently the AWT one for the JVM.

/**
* @throws PiktWrongArgumentTypeException if [image] is not an [Image], or is not a [WritableImage] and [requireWritable] is `true`.
*/
private fun checkImageType(image: Any, requireWritable: Boolean = false, reference: Any) {
if (image !is Image || (requireWritable && image !is WritableImage)) {
throw PiktWrongArgumentTypeException(
parameterName = "image",
argumentValue = image,
expectedType = if (requireWritable) WRITABLE_IMAGE else IMAGE,
reference
)
}
}

/**
* @throws PiktWrongArgumentTypeException if [x] and/or [y] are not numbers.
*/
private fun checkCoordinatesType(x: Any, y: Any, reference: Any) {
if (x !is Int) {
throw PiktWrongArgumentTypeException(
parameterName = "x",
argumentValue = x,
expectedType = NUMBER,
reference
)
}
if (y !is Int) {
throw PiktWrongArgumentTypeException(
parameterName = "y",
argumentValue = y,
expectedType = NUMBER,
reference
)
}
}

/**
* Instantiates a new writable [Image].
* @param width image width as [Int]
Expand Down Expand Up @@ -52,46 +90,58 @@ fun newImage(pathOrFile: Any): WritableImage = AwtImage.fromFile(newFile(pathOrF
* @throws PiktIOException if the image could not be saved
*/
fun saveImage(image: Any, pathOrFile: Any) {
if (image !is WritableImage) {
throw PiktWrongArgumentTypeException(
parameterName = "image",
argumentValue = image,
expectedType = WRITABLE_IMAGE,
reference = object {}
)
}

image.save(newFile(pathOrFile))
checkImageType(image, requireWritable = true, reference = object {})
(image as WritableImage).save(newFile(pathOrFile))
}

/**
* @return width of [image]
*/
fun imageWidth(image: Any): Int {
if (image !is Image) {
throw PiktWrongArgumentTypeException(
parameterName = "image",
argumentValue = image,
expectedType = IMAGE,
reference = object {}
)
}

return image.width
checkImageType(image, reference = object {})
return (image as Image).width
}

/**
* @return height of [image]
*/
fun imageHeight(image: Any): Int {
if (image !is Image) {
checkImageType(image, reference = object {})
return (image as Image).height
}

/**
* @param x X coordinate
* @param y Y coordinate
* @return the color of the [image] at the given coordinate
* @throws PiktIndexOutOfBoundsException if one of the coordinates is negative or greater than the image size
*/
fun getImageColor(image: Any, x: Any, y: Any): Color {
checkImageType(image, reference = object {})
checkCoordinatesType(x, y, object {})

return (image as Image).getColor(x as Int, y as Int)
}

/**
* Changes the color of a pixel of the [image] at the given coordinates.
* @param x X coordinate
* @param y Y coordinate
* @param color color to set
* @throws PiktIndexOutOfBoundsException if one of the coordinates is negative or greater than the image size
*/
fun setImageColor(image: Any, x: Any, y: Any, color: Any) {
checkImageType(image, requireWritable = true, reference = object {})
checkCoordinatesType(x, y, reference = object {})

if (color !is Color) {
throw PiktWrongArgumentTypeException(
parameterName = "image",
argumentValue = image,
expectedType = IMAGE,
parameterName = "color",
argumentValue = color,
expectedType = COLOR,
reference = object {}
)
}

return image.height
}
return (image as WritableImage).setColor(x as Int, y as Int, color)
}
10 changes: 10 additions & 0 deletions imagelib/src/main/kotlin/pikt/imagelib/WritableImage.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package pikt.imagelib

import pikt.error.PiktIOException
import pikt.error.PiktIndexOutOfBoundsException
import java.io.File

/**
* An image that can be modified and saved on file.
*/
interface WritableImage : Image {

/**
* Changes the color of a pixel.
* @param x X coordinate
* @param y Y coordinate
* @param color color to set at the given coordinates
* @throws PiktIndexOutOfBoundsException if one of the coordinates is negative or greater than the image size
*/
fun setColor(x: Int, y: Int, color: Color)

/**
* Saves this image to file.
* @param file file to save the image to
Expand Down
4 changes: 3 additions & 1 deletion imagelib/src/main/resources/colors.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
newImage=B41EFF
saveImage=681EFF
imageWidth=
imageHeight=
imageHeight=
getImageColor=
setImageColor=
4 changes: 2 additions & 2 deletions stdlib/src/main/kotlin/pikt/stdlib/Struct.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ open class Struct(vararg members: Pair<String, Any>) {
* @param property member of this struct associated with the desired value
* @throws PiktNoSuchElementException if [property]'s `toString()` is not a member of this struct
*/
operator fun get(property: Any) = properties.mapGet(property.toString())
open operator fun get(property: Any) = properties.mapGet(property.toString())
?: throw PiktNoSuchElementException(property, reference = object {})

/**
Expand All @@ -31,7 +31,7 @@ open class Struct(vararg members: Pair<String, Any>) {
* @param value new value to overwrite
* @throws PiktNoSuchElementException if [property]'s `toString()` is not a member of this struct
*/
operator fun set(property: Any, value: Any) {
open operator fun set(property: Any, value: Any) {
val key = property.toString()
if(properties.containsKey(key)) {
properties.mapSet(key, value)
Expand Down

0 comments on commit 9ee3e51

Please sign in to comment.