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