└── Seam Carving └── task ├── test ├── blue-negative.png ├── small-negative.png └── trees-negative.png └── src └── seamcarving ├── EnergyPoint.kt ├── TransformationType.kt ├── Main.kt ├── Seam.kt ├── Converter.kt ├── EnergyArray.kt └── BufferedImageExtension.kt /Seam Carving/task/test/blue-negative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdoc/Seam-Carving/master/Seam Carving/task/test/blue-negative.png -------------------------------------------------------------------------------- /Seam Carving/task/test/small-negative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdoc/Seam-Carving/master/Seam Carving/task/test/small-negative.png -------------------------------------------------------------------------------- /Seam Carving/task/test/trees-negative.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vdoc/Seam-Carving/master/Seam Carving/task/test/trees-negative.png -------------------------------------------------------------------------------- /Seam Carving/task/src/seamcarving/EnergyPoint.kt: -------------------------------------------------------------------------------- 1 | package seamcarving.util 2 | 3 | data class EnergyPoint( 4 | val x: Int, val y: Int, 5 | val energy: Double, 6 | val previousX: Int? = null 7 | ) -------------------------------------------------------------------------------- /Seam Carving/task/src/seamcarving/TransformationType.kt: -------------------------------------------------------------------------------- 1 | package seamcarving.util 2 | 3 | enum class TransformationType { 4 | NEGATIVE, 5 | ENERGY, 6 | ADD_VERTICAL_SEAM, 7 | ADD_HORIZONTAL_SEAM 8 | } -------------------------------------------------------------------------------- /Seam Carving/task/src/seamcarving/Main.kt: -------------------------------------------------------------------------------- 1 | package seamcarving 2 | 3 | import seamcarving.util.Converter 4 | import seamcarving.util.TransformationType 5 | 6 | fun main(args: Array) { 7 | require(args.size == 4) { "USAGE: -in inputFileName -out outputFileName" } 8 | 9 | val inputFileName = if (args[0] == "-in") { 10 | args[1] 11 | } else { 12 | throw IllegalStateException("ERROR: missing required '-in' parameter!") 13 | } 14 | 15 | val outputFileName = if (args[2] == "-out") { 16 | args[3] 17 | } else { 18 | throw IllegalStateException("ERROR: missing required '-out' parameter!") 19 | } 20 | 21 | try { 22 | Converter.convert(inputFileName, outputFileName, TransformationType.ADD_HORIZONTAL_SEAM) 23 | } catch (exception: Exception) { 24 | exception.printStackTrace() 25 | } 26 | } -------------------------------------------------------------------------------- /Seam Carving/task/src/seamcarving/Seam.kt: -------------------------------------------------------------------------------- 1 | package seamcarving.util 2 | 3 | data class Seam(val width: Int, val height: Int) : Iterable { 4 | private val points = mutableListOf() 5 | 6 | fun add(x: Int, y: Int, energy: Double, previousX: Int? = null) { 7 | require(x in 0 until width && y in 0 until height && energy >= 0.0) { 8 | "Illegal coordinate ($x, $y)!" 9 | } 10 | 11 | points.add(EnergyPoint(x, y, energy, previousX)) 12 | } 13 | 14 | fun add(point: EnergyPoint) = add(point.x, point.y, point.energy, point.previousX) 15 | 16 | override fun iterator(): Iterator = object: Iterator { 17 | private val pointsIterator = points.iterator() 18 | 19 | override fun hasNext() = pointsIterator.hasNext() 20 | 21 | override fun next() = pointsIterator.next() 22 | } 23 | } -------------------------------------------------------------------------------- /Seam Carving/task/src/seamcarving/Converter.kt: -------------------------------------------------------------------------------- 1 | package seamcarving.util 2 | 3 | import java.awt.Color 4 | import java.awt.Graphics2D 5 | import java.awt.image.BufferedImage 6 | import java.io.File 7 | import javax.imageio.ImageIO 8 | import kotlin.math.sqrt 9 | 10 | object Converter { 11 | fun convert(inputFileName: String, outputFileName: String, 12 | transformationType: TransformationType) { 13 | 14 | val transformation = when (transformationType) { 15 | TransformationType.NEGATIVE -> BufferedImage::negative 16 | TransformationType.ENERGY -> BufferedImage::energy 17 | TransformationType.ADD_VERTICAL_SEAM -> BufferedImage::addVerticalSeam 18 | TransformationType.ADD_HORIZONTAL_SEAM -> BufferedImage::addHorizontalSeam 19 | } 20 | 21 | process(inputFileName, outputFileName, transformation) 22 | } 23 | 24 | private fun process(inputFileName: String, outputFileName: String, 25 | transformation: BufferedImage.() -> BufferedImage) { 26 | 27 | val image = readImage(inputFileName) 28 | 29 | val processedImage = image.transformation() 30 | 31 | saveImage(outputFileName, processedImage) 32 | } 33 | 34 | private fun readImage(inputFileName: String): BufferedImage { 35 | val inputFile = File(inputFileName) 36 | 37 | require(inputFile.exists() && inputFile.canRead()) { "Can't read input file!" } 38 | 39 | return ImageIO.read(inputFile) 40 | } 41 | 42 | private fun saveImage(outputFileName: String, image: BufferedImage) { 43 | val outputFile = File(outputFileName) 44 | 45 | ImageIO.write(image, "png", outputFile) 46 | } 47 | } -------------------------------------------------------------------------------- /Seam Carving/task/src/seamcarving/EnergyArray.kt: -------------------------------------------------------------------------------- 1 | package seamcarving.util 2 | 3 | import java.lang.Math.max 4 | import java.lang.Math.min 5 | 6 | data class EnergyArray(val width: Int, val height: Int) { 7 | private val array = Array(height) { DoubleArray(width) } 8 | 9 | var maxEnergy: Double = 0.0 10 | private set 11 | 12 | operator fun get(x: Int, y: Int): Double { 13 | require(x in 0 until width && y in 0 until height) { "Illegal coordinate ($x, $y)!" } 14 | 15 | return array[y][x] 16 | } 17 | 18 | operator fun set(x: Int, y: Int, energy: Double) { 19 | require(x in 0 until width && y in 0 until height) { "Illegal coordinate ($x, $y)!" } 20 | 21 | array[y][x] = energy 22 | maxEnergy = if (energy > maxEnergy) energy else maxEnergy 23 | } 24 | 25 | val verticalSeam 26 | get(): Seam { 27 | val seam = Seam(width, height) 28 | 29 | // based on (implementation in Python): 30 | // https://avikdas.com/2019/05/14/real-world-dynamic-programming-seam-carving.html 31 | 32 | // as is it needs some refactoring, but for now it works :-) 33 | 34 | val seamEnergies = mutableListOf>() 35 | var seamEnergiesRow = mutableListOf() 36 | 37 | for (x in 0 until width) { 38 | seamEnergiesRow.add(EnergyPoint(x, 0, array[0][x])) 39 | } 40 | 41 | seamEnergies.add(seamEnergiesRow) 42 | 43 | for (y in 1 until height) { 44 | val rowEnergies = array[y] 45 | 46 | seamEnergiesRow = mutableListOf() 47 | 48 | for (x in 0 until width) { 49 | val xLeft = (x - 1).coerceAtLeast(0) 50 | val xRight = (x + 1).coerceAtMost(width - 1) 51 | 52 | var minParentX = xLeft 53 | var minSeamEnergy = seamEnergies[y - 1][minParentX].energy 54 | 55 | for (i in xLeft + 1 .. xRight) { 56 | if (seamEnergies[y - 1][i].energy < minSeamEnergy) { 57 | minParentX = i 58 | minSeamEnergy = seamEnergies[y - 1][i].energy 59 | } 60 | } 61 | 62 | seamEnergiesRow.add(EnergyPoint(x, y, rowEnergies[x] + minSeamEnergy, minParentX)) 63 | } 64 | 65 | seamEnergies.add(seamEnergiesRow) 66 | } 67 | 68 | var minSeamEndX = 0 69 | var minSeamEndEnergy = seamEnergies[height - 1][0].energy 70 | for (x in 1 until width) { 71 | if (seamEnergies[height - 1][x].energy < minSeamEndEnergy) { 72 | minSeamEndX = x 73 | minSeamEndEnergy = seamEnergies[height - 1][x].energy 74 | } 75 | } 76 | 77 | val path = mutableListOf() 78 | var seamPointX: Int? = minSeamEndX 79 | for (y in height - 1 downTo 0) { 80 | if (seamPointX != null) { 81 | val energyPoint = seamEnergies[y][seamPointX] 82 | path.add(EnergyPoint(energyPoint.x, energyPoint.y, energyPoint.energy, energyPoint.previousX)) 83 | seamPointX = energyPoint.previousX 84 | } 85 | } 86 | 87 | for (point in path.reversed()) { 88 | seam.add(point) 89 | } 90 | 91 | return seam 92 | } 93 | } -------------------------------------------------------------------------------- /Seam Carving/task/src/seamcarving/BufferedImageExtension.kt: -------------------------------------------------------------------------------- 1 | package seamcarving.util 2 | 3 | import java.awt.Color 4 | import java.awt.Graphics2D 5 | import java.awt.image.BufferedImage 6 | import kotlin.math.sqrt 7 | 8 | fun createImage(width: Int, height: Int): BufferedImage { 9 | val bufferedImage = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) 10 | 11 | val graphics : Graphics2D = bufferedImage.createGraphics() 12 | 13 | graphics.paint = Color.BLACK 14 | graphics.fillRect(0, 0, bufferedImage.width, bufferedImage.height) 15 | graphics.paint = Color.RED 16 | graphics.drawLine(0, 0, bufferedImage.width - 1, bufferedImage.height - 1) 17 | graphics.drawLine(0, bufferedImage.height - 1, bufferedImage.width - 1, 0) 18 | 19 | return bufferedImage 20 | } 21 | 22 | fun BufferedImage.negative(): BufferedImage { 23 | val outputImage = createImage(width, height) 24 | 25 | for (y in 0 until height) { 26 | for (x in 0 until width) { 27 | val inputPixel = getRGB(x, y) 28 | val inputColor = Color(inputPixel, true) 29 | val r = 255 - inputColor.red 30 | val g = 255 - inputColor.green 31 | val b = 255 - inputColor.blue 32 | val outputColor = Color(r, g, b) 33 | outputImage.setRGB(x, y, outputColor.rgb) 34 | } 35 | } 36 | 37 | return outputImage 38 | } 39 | 40 | fun BufferedImage.deltaXSquared(x: Int, y: Int): Int { 41 | val xx = when (x) { 42 | 0 -> 1 43 | width - 1 -> width - 2 44 | else -> x 45 | } 46 | 47 | val inputColorX1 = Color(getRGB(xx + 1, y), true) 48 | val inputColorX2 = Color(getRGB(xx - 1, y), true) 49 | 50 | val rx = inputColorX1.red - inputColorX2.red 51 | val gx = inputColorX1.green - inputColorX2.green 52 | val bx = inputColorX1.blue - inputColorX2.blue 53 | 54 | return (rx * rx) + (gx * gx) + (bx * bx) 55 | } 56 | 57 | fun BufferedImage.deltaYSquared(x: Int, y: Int): Int { 58 | val yy = when (y) { 59 | 0 -> 1 60 | height - 1 -> height - 2 61 | else -> y 62 | } 63 | 64 | val inputPixelY1 = Color(getRGB(x, yy + 1), true) 65 | val inputPixelY2 = Color(getRGB(x, yy - 1), true) 66 | 67 | val ry = inputPixelY1.red - inputPixelY2.red 68 | val gy = inputPixelY1.green - inputPixelY2.green 69 | val by = inputPixelY1.blue - inputPixelY2.blue 70 | 71 | return (ry * ry) + (gy * gy) + (by * by) 72 | } 73 | 74 | fun BufferedImage.energyArray(): EnergyArray { 75 | val energyArray = EnergyArray(width, height) 76 | 77 | for (y in 0 until height) { 78 | for (x in 0 until width) { 79 | val dx2 = deltaXSquared(x, y) 80 | val dy2 = deltaYSquared(x, y) 81 | 82 | val energy = sqrt((dx2 + dy2).toDouble()) 83 | 84 | energyArray[x, y] = energy 85 | } 86 | } 87 | 88 | return energyArray 89 | } 90 | 91 | fun BufferedImage.energy(): BufferedImage { 92 | val outputImage = createImage(width, height) 93 | 94 | val energyArray = energyArray() 95 | 96 | for (y in 0 until height) { 97 | for (x in 0 until width) { 98 | val energy = energyArray[x, y] 99 | 100 | val intensity = (255.0 * energy / energyArray.maxEnergy).toInt() 101 | 102 | val r = intensity 103 | val g = intensity 104 | val b = intensity 105 | 106 | val outputColor = Color(r, g, b) 107 | 108 | outputImage.setRGB(x, y, outputColor.rgb) 109 | } 110 | } 111 | 112 | return outputImage 113 | } 114 | 115 | fun BufferedImage.addVerticalSeam(): BufferedImage { 116 | val outputImage = createImage(width, height) 117 | 118 | // rewrite original image 119 | 120 | for (y in 0 until height) { 121 | for (x in 0 until width) { 122 | val inputPixel = getRGB(x, y) 123 | val inputColor = Color(inputPixel, true) 124 | outputImage.setRGB(x, y, inputColor.rgb) 125 | } 126 | } 127 | 128 | // add vertical seam 129 | 130 | val energyArray = energyArray() 131 | val seam = energyArray.verticalSeam 132 | 133 | for ((x, y) in seam) { 134 | outputImage.setRGB(x, y, Color.RED.rgb) 135 | } 136 | 137 | return outputImage 138 | } 139 | 140 | private fun BufferedImage.transpose(): BufferedImage { 141 | val outputImage = createImage(height, width) 142 | 143 | for (y in 0 until height) { 144 | for (x in 0 until width) { 145 | val inputPixel = getRGB(x, y) 146 | val inputColor = Color(inputPixel, true) 147 | outputImage.setRGB(y, x, inputColor.rgb) 148 | } 149 | } 150 | 151 | return outputImage 152 | } 153 | 154 | fun BufferedImage.addHorizontalSeam(): BufferedImage = 155 | this.transpose().addVerticalSeam().transpose() --------------------------------------------------------------------------------