diff 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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/name/blackcap/imageprep/RotateDialog.kt	Thu Jul 16 19:57:23 2020 -0700
@@ -0,0 +1,143 @@
+/*
+ * 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()
+                }
+            }
+        }
+    }
+}