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