view src/name/blackcap/imageprep/RotateDialog.kt @ 0:e0efe7848130

Initial commit. Untested!
author David Barts <davidb@stashtea.com>
date Thu, 16 Jul 2020 19:57:23 -0700
parents
children 0bded24f746e
line wrap: on
line source

/*
 * Represents a file being edited (rotated)
 */
package name.blackcap.imageprep

import java.awt.Graphics2D
import java.awt.RenderingHints
import java.awt.image.BufferedImage
import java.io.File
import java.io.IOException
import javax.imageio.ImageIO
import javax.swing.*
import kotlin.math.*

class RotateDialog(val file: File, initialImage: BufferedImage) : JDialog(Application.mainFrame) {
    private val BW = 9
    private val BW2 = BW * 2
    private val MINWIDTH = 512
    private val MINHEIGHT = 384

    private class DrawingPane() : JPanel() {
        var image: BufferedImage = initialImage
        set(value) {
            field = value
            revalidate()
            repaint()
        }

        override val preferredSize
        get { return image.preferredSize }

        protected paintComponent(g: Graphics): Unit {
            g.drawImage(image, 0, 0, null)
        }
    }
    private val drawingPane = DrawingPane()

    val image: BufferedImage
    get() { return drawingPane.image }

    private val r90cw = JButton("90° CW").apply {
        it.addActionListener(ActionListener { doRotate(90) })
    }

    private val r180 = JButton("180°").apply {
        it.addActionListener(ActionListener { doRotate(180) })
    }

    private val r90ccw = JButton("90° CCW").apply {
        it.addActionListener(ActionListener { doRotate(270) })
    }

    /* Theoretically, this should do the rotation in a background thread.
       Practically, that is fraught with difficulties, as it involves
       manipulating data used by Swing itself. Since the size of the images
       being rotated are small, we do it in the foreground. */
    private fun doRotate(deg: Int) {
        // https://stackoverflow.com/questions/15927014/rotating-an-image-90-degrees-in-java
        val rad = java.lang.Math.toRadians(deg.toDouble())
        val sinx = sin(rad)
        val cosx = cos(rad)
        val w = floor(image.width * cosx + image.height * sinx).toInt()
        val h = floor(image.height * cosx + image.width * sinx).toInt()
        val rotatedImage = BufferedImage(w, h, image.type)
        rotatedImage.createGraphics().run {
            translate((w - image.width) / 2, (h - image.height) / 2)
            rotate(rad, image.width / 2, image.height / 2)
            drawRenderedImage(image, null)
            dispose()
        }
        drawingPane.image = rotatedImage
    }

    init {
        defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE
        title = "Untitled"
        contentPane.apply {
            layout = BoxLayout(this, BoxLayout.Y_AXIS)
            add(JScrollpane(drawingPane).apply {
                alignmentX = JScrollPane.CENTER_ALIGNMENT
                addBorder(BorderFactory.createEmptyBorder(BW2, BW2, BW, BW2))
                verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS
                horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS
                background = Application.mainFrame.background
            })
            add(Box(BoxLayout.X_AXIS).apply {
                alignmentX = Box.CENTER_ALIGNMENT
                addBorder(BorderFactory.createEmptyBorder(BW, BW2, BW2, BW2))
                add(JLabel("Rotate:"))
                add(Box.createHorizontalGlue())
                add(r90cw)
                add(Box.createHorizontalGlue())
                add(r180)
                add(Box.createHorizontalGlue())
                add(r90ccw)
            })
        }
        pack()
    }

    companion object Factory {
        /**
         * Make a dialog asynchronously.
         *
         * @param input java.io.File to read for image.
         */
        fun makeDialog(input: File): Unit {
            Application.mainFrame.useWaitCursor()
            swingWorker<Pair<BufferedImage?, IOException?> {
                inBackground {
                    try {
                        val imageIn = ImageIO.read(input) /* IOException */
                        val ratio = Settings.maxDimension.toDouble() / max(imageIn.width, imageIn.height).toDouble()
                        if (ratio >= 1.0) {
                            null
                        } else {
                            val nWidth = (imageIn.width * ratio).toInt()
                            val nHeight = (imageIn.height * ratio).toInt()
                            val imageOut = BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_RGB)
                            val graphics = imageOut.createGraphics().apply {
                                setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
                                setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
                                setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
                            }
                            graphics.drawImage(imageIn, 0, 0, nWidth, nHeight, null)
                            Pair(imageOut, null)
                        }
                    } catch (e: IOException) {
                        Pair(null, e)
                    }
                }
                whenDone {
                    Application.mainFrame.useNormalCursor()
                    val (image, error) = get()
                    if (error != null)
                        ioExceptionDialog(Application.mainFrame, input, "read", error)
                    if (image != null)
                        RotateDialog(input, image).title = input.getName()
                }
            }
        }
    }
}