0
|
1 /*
|
|
2 * Represents a file being edited (rotated)
|
|
3 */
|
|
4 package name.blackcap.imageprep
|
|
5
|
1
|
6 import java.awt.Dimension
|
|
7 import java.awt.Graphics
|
0
|
8 import java.awt.Graphics2D
|
|
9 import java.awt.RenderingHints
|
1
|
10 import java.awt.event.ActionListener
|
0
|
11 import java.awt.image.BufferedImage
|
|
12 import java.io.File
|
|
13 import java.io.IOException
|
3
|
14 import java.util.logging.Level
|
|
15 import java.util.logging.Logger
|
0
|
16 import javax.imageio.ImageIO
|
|
17 import javax.swing.*
|
|
18 import kotlin.math.*
|
|
19
|
|
20 class RotateDialog(val file: File, initialImage: BufferedImage) : JDialog(Application.mainFrame) {
|
|
21 private val BW = 9
|
|
22 private val BW2 = BW * 2
|
|
23 private val MINWIDTH = 512
|
|
24 private val MINHEIGHT = 384
|
|
25
|
1
|
26 private class DrawingPane(initialImage: BufferedImage) : JPanel() {
|
0
|
27 var image: BufferedImage = initialImage
|
|
28 set(value) {
|
|
29 field = value
|
|
30 revalidate()
|
|
31 repaint()
|
|
32 }
|
|
33
|
1
|
34 override fun getPreferredSize() = Dimension(image.width, image.height)
|
0
|
35
|
1
|
36 override protected fun paintComponent(g: Graphics): Unit {
|
0
|
37 g.drawImage(image, 0, 0, null)
|
|
38 }
|
|
39 }
|
1
|
40 private val drawingPane = DrawingPane(initialImage)
|
0
|
41
|
|
42 val image: BufferedImage
|
|
43 get() { return drawingPane.image }
|
|
44
|
1
|
45 private val r90cw = JButton("90° CW").also {
|
0
|
46 it.addActionListener(ActionListener { doRotate(90) })
|
|
47 }
|
|
48
|
1
|
49 private val r180 = JButton("180°").also {
|
0
|
50 it.addActionListener(ActionListener { doRotate(180) })
|
|
51 }
|
|
52
|
1
|
53 private val r90ccw = JButton("90° CCW").also {
|
0
|
54 it.addActionListener(ActionListener { doRotate(270) })
|
|
55 }
|
|
56
|
|
57 /* Theoretically, this should do the rotation in a background thread.
|
|
58 Practically, that is fraught with difficulties, as it involves
|
|
59 manipulating data used by Swing itself. Since the size of the images
|
|
60 being rotated are small, we do it in the foreground. */
|
|
61 private fun doRotate(deg: Int) {
|
3
|
62 rootPane.defaultButton = null
|
0
|
63 // https://stackoverflow.com/questions/15927014/rotating-an-image-90-degrees-in-java
|
3
|
64 if (deg % 90 != 0) {
|
|
65 val barf = "${deg} not a multiple of 90!"
|
|
66 LOGGER.log(Level.SEVERE, barf)
|
|
67 throw AssertionError(barf)
|
|
68 }
|
0
|
69 val rad = java.lang.Math.toRadians(deg.toDouble())
|
3
|
70 val (w, h) = if (deg % 180 == 0) Pair(image.width, image.height) else Pair(image.height, image.width)
|
0
|
71 val rotatedImage = BufferedImage(w, h, image.type)
|
|
72 rotatedImage.createGraphics().run {
|
|
73 translate((w - image.width) / 2, (h - image.height) / 2)
|
1
|
74 rotate(rad, image.width.toDouble()/2.0, image.height.toDouble()/2.0)
|
0
|
75 drawRenderedImage(image, null)
|
|
76 dispose()
|
|
77 }
|
|
78 drawingPane.image = rotatedImage
|
|
79 }
|
|
80
|
|
81 init {
|
|
82 defaultCloseOperation = JDialog.DISPOSE_ON_CLOSE
|
|
83 title = "Untitled"
|
|
84 contentPane.apply {
|
|
85 layout = BoxLayout(this, BoxLayout.Y_AXIS)
|
1
|
86 add(JScrollPane(drawingPane).apply {
|
0
|
87 alignmentX = JScrollPane.CENTER_ALIGNMENT
|
|
88 addBorder(BorderFactory.createEmptyBorder(BW2, BW2, BW, BW2))
|
|
89 verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS
|
|
90 horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS
|
|
91 background = Application.mainFrame.background
|
|
92 })
|
|
93 add(Box(BoxLayout.X_AXIS).apply {
|
|
94 alignmentX = Box.CENTER_ALIGNMENT
|
|
95 addBorder(BorderFactory.createEmptyBorder(BW, BW2, BW2, BW2))
|
|
96 add(JLabel("Rotate:"))
|
|
97 add(Box.createHorizontalGlue())
|
|
98 add(r90cw)
|
|
99 add(Box.createHorizontalGlue())
|
|
100 add(r180)
|
|
101 add(Box.createHorizontalGlue())
|
|
102 add(r90ccw)
|
|
103 })
|
|
104 }
|
3
|
105 rootPane.defaultButton = null
|
0
|
106 pack()
|
|
107 }
|
|
108
|
|
109 companion object Factory {
|
|
110 /**
|
|
111 * Make a dialog asynchronously.
|
|
112 *
|
|
113 * @param input java.io.File to read for image.
|
|
114 */
|
|
115 fun makeDialog(input: File): Unit {
|
|
116 Application.mainFrame.useWaitCursor()
|
1
|
117 swingWorker<Pair<BufferedImage?, IOException?>> {
|
0
|
118 inBackground {
|
|
119 try {
|
|
120 val imageIn = ImageIO.read(input) /* IOException */
|
|
121 val ratio = Settings.maxDimension.toDouble() / max(imageIn.width, imageIn.height).toDouble()
|
|
122 if (ratio >= 1.0) {
|
1
|
123 Pair(null, null)
|
0
|
124 } else {
|
|
125 val nWidth = (imageIn.width * ratio).toInt()
|
|
126 val nHeight = (imageIn.height * ratio).toInt()
|
|
127 val imageOut = BufferedImage(nWidth, nHeight, BufferedImage.TYPE_INT_RGB)
|
|
128 val graphics = imageOut.createGraphics().apply {
|
|
129 setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR)
|
|
130 setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
|
|
131 setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
|
132 }
|
|
133 graphics.drawImage(imageIn, 0, 0, nWidth, nHeight, null)
|
|
134 Pair(imageOut, null)
|
|
135 }
|
|
136 } catch (e: IOException) {
|
|
137 Pair(null, e)
|
|
138 }
|
|
139 }
|
|
140 whenDone {
|
|
141 Application.mainFrame.useNormalCursor()
|
|
142 val (image, error) = get()
|
|
143 if (error != null)
|
|
144 ioExceptionDialog(Application.mainFrame, input, "read", error)
|
1
|
145 else if (image != null)
|
3
|
146 RotateDialog(input, image).run {
|
|
147 title = input.name
|
|
148 setVisible(true)
|
|
149 }
|
1
|
150 else
|
|
151 JOptionPane.showMessageDialog(Application.mainFrame,
|
|
152 "Image is too small to be scaled.",
|
|
153 "Warning", JOptionPane.WARNING_MESSAGE)
|
0
|
154 }
|
|
155 }
|
|
156 }
|
|
157 }
|
|
158 }
|