001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2005, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.gui.swing.image;
018:
019: // J2SE dependencies
020: import java.awt.Color;
021: import java.awt.image.DataBuffer;
022: import java.awt.image.Raster;
023: import java.awt.image.RenderedImage;
024: import java.io.IOException;
025: import java.io.ObjectInputStream;
026: import java.text.NumberFormat;
027: import java.text.ParseException;
028: import javax.swing.event.TableModelEvent;
029: import javax.swing.event.TableModelListener;
030: import javax.swing.table.AbstractTableModel;
031:
032: /**
033: * A table model for image sample values (or pixels). This model is serialiable if the
034: * underlying {@link RenderedImage} is serializable.
035: *
036: * @since 2.3
037: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/image/ImageTableModel.java $
038: * @version $Id: ImageTableModel.java 20883 2006-08-07 13:48:09Z jgarnett $
039: * @author Martin Desruisseaux
040: *
041: * @todo Should supports deferred execution: request for a new tile should wait some maximal amount
042: * of time (e.g. 0.1 seconds). If the tile is not yet available after that time, the model
043: * should returns {@code null} at this time and send a "data changed" event later when the
044: * tile is finally available.
045: *
046: * @see ImageSampleValues
047: */
048: public class ImageTableModel extends AbstractTableModel {
049: /**
050: * Serial number for compatibility with different versions.
051: */
052: private static final long serialVersionUID = -408603520054548181L;
053:
054: /**
055: * The image to display.
056: */
057: private RenderedImage image;
058:
059: /**
060: * The format to use for formatting sample values.
061: */
062: private NumberFormat format = NumberFormat.getNumberInstance();
063:
064: /**
065: * The format to use for formatting line and column labels.
066: */
067: private NumberFormat titleFormat = NumberFormat
068: .getIntegerInstance();
069:
070: /**
071: * The band to show.
072: */
073: private int band;
074:
075: /**
076: * Image properites computed by {@link #update}. Those properties are used everytime
077: * {@link #getValueAt} is invoked, which is why we cache them.
078: */
079: private transient int minX, minY, maxX, maxY, tileGridXOffset,
080: tileGridYOffset, tileWidth, tileHeight, dataType;
081:
082: /**
083: * The type of sample values. Is computed by {@link #update}.
084: */
085: private transient Class type = Number.class;
086:
087: /**
088: * The row and column names. Will be created only when first needed.
089: */
090: private transient String[] rowNames, columnNames;
091:
092: /**
093: * The pixel values as an object of the color model transfert type.
094: * Cached for avoiding to much creation of the same object.
095: */
096: private transient Object pixel;
097:
098: /**
099: * Creates a new table model.
100: */
101: public ImageTableModel() {
102: }
103:
104: /**
105: * Creates a new table model for the specified image.
106: */
107: public ImageTableModel(final RenderedImage image) {
108: setRenderedImage(image);
109: }
110:
111: /**
112: * Returns the image to display, or {@code null} if none.
113: */
114: public RenderedImage getRenderedImage() {
115: return image;
116: }
117:
118: /**
119: * Sets the image to display.
120: */
121: public void setRenderedImage(final RenderedImage image) {
122: this .image = image;
123: pixel = null;
124: rowNames = null;
125: columnNames = null;
126: final int digits = update();
127: format.setMinimumFractionDigits(digits);
128: format.setMaximumFractionDigits(digits);
129: fireTableStructureChanged();
130: }
131:
132: /**
133: * Updates transient fields after an image change. Also invoked after deserialization.
134: * Returns the number of fraction digits to use for the format (to be ignored in the
135: * case of deserialization, since the format is serialized).
136: */
137: private int update() {
138: int digits = 0;
139: if (image != null) {
140: minX = image.getMinX();
141: minY = image.getMinY();
142: maxX = image.getWidth() + minX;
143: maxY = image.getHeight() + minY;
144: tileGridXOffset = image.getTileGridXOffset();
145: tileGridYOffset = image.getTileGridYOffset();
146: tileWidth = image.getTileWidth();
147: tileHeight = image.getTileHeight();
148: dataType = image.getSampleModel().getDataType();
149: switch (dataType) {
150: case DataBuffer.TYPE_BYTE: // Fall through
151: case DataBuffer.TYPE_SHORT: // Fall through
152: case DataBuffer.TYPE_USHORT: // Fall through
153: case DataBuffer.TYPE_INT:
154: type = Integer.class;
155: break;
156: case DataBuffer.TYPE_FLOAT:
157: type = Float.class;
158: digits = 2;
159: break;
160: case DataBuffer.TYPE_DOUBLE:
161: type = Double.class;
162: digits = 3;
163: break;
164: default:
165: type = Number.class;
166: break;
167: }
168: } else {
169: type = Number.class;
170: }
171: return digits;
172: }
173:
174: /**
175: * Recomputes transient fields after deserializations.
176: */
177: private void readObject(final ObjectInputStream in)
178: throws IOException, ClassNotFoundException {
179: in.defaultReadObject();
180: update();
181: }
182:
183: /**
184: * Returns the band to display.
185: */
186: public int getBand() {
187: return band;
188: }
189:
190: /**
191: * Set the band to display.
192: */
193: public void setBand(final int band) {
194: if (band < 0
195: || (image != null && band >= image.getSampleModel()
196: .getNumBands())) {
197: throw new IndexOutOfBoundsException();
198: }
199: this .band = band;
200: fireTableDataChanged();
201: }
202:
203: /**
204: * Returns the format to use for formatting sample values.
205: */
206: public NumberFormat getNumberFormat() {
207: return format;
208: }
209:
210: /**
211: * Sets the format to use for formatting sample values.
212: */
213: public void setNumberFormat(final NumberFormat format) {
214: this .format = format;
215: fireTableDataChanged();
216: }
217:
218: /**
219: * Returns the number of rows in the model, which is
220: * the {@linkplain RenderedImage#getHeight image height}.
221: */
222: public int getRowCount() {
223: return (image != null) ? image.getHeight() : 0;
224: }
225:
226: /**
227: * Returns the number of columns in the model, which is
228: * the {@linkplain RenderedImage#getWidth image width}.
229: */
230: public int getColumnCount() {
231: return (image != null) ? image.getWidth() : 0;
232: }
233:
234: /**
235: * Returns the row name. The names are the pixel row number, starting at
236: * the {@linkplain RenderedImage#getMinY min y} value.
237: */
238: public String getRowName(final int row) {
239: if (rowNames == null) {
240: rowNames = new String[image.getHeight()];
241: }
242: String candidate = rowNames[row];
243: if (candidate == null) {
244: rowNames[row] = candidate = titleFormat.format(minY + row);
245: }
246: return candidate;
247: }
248:
249: /**
250: * Returns the column name. The names are the pixel column number, starting at
251: * the {@linkplain RenderedImage#getMinX min x} value.
252: */
253: public String getColumnName(final int column) {
254: if (columnNames == null) {
255: if (image == null) {
256: return super .getColumnName(column);
257: }
258: columnNames = new String[image.getWidth()];
259: }
260: String candidate = columnNames[column];
261: if (candidate == null) {
262: columnNames[column] = candidate = titleFormat.format(minX
263: + column);
264: }
265: return candidate;
266: }
267:
268: /**
269: * Returns a column given its name.
270: */
271: public int findColumn(final String name) {
272: if (image != null)
273: try {
274: return titleFormat.parse(name).intValue() - minX;
275: } catch (ParseException exception) {
276: // Ignore; fallback on the default algorithm.
277: }
278: return super .findColumn(name);
279: }
280:
281: /**
282: * Returns the type of sample values regardless of column index.
283: */
284: public Class getColumnClass(final int column) {
285: return type;
286: }
287:
288: /**
289: * Returns the raster at the specified pixel location, or {@code null} if none.
290: * The (<var>x</var>, <var>y</var>) <strong>must</strong> be additionned with
291: * {@link #minX} and {@link #minY}.
292: */
293: private final Raster getRasterAt(final int y, final int x) {
294: if (x < minX || x >= maxX || y < minY || y >= maxY) {
295: return null;
296: }
297: int tx = x - tileGridXOffset;
298: if (x < 0)
299: tx += 1 - tileWidth;
300: int ty = y - tileGridYOffset;
301: if (y < 0)
302: ty += 1 - tileHeight;
303: return image.getTile(tx / tileWidth, ty / tileHeight);
304: }
305:
306: /**
307: * Returns the sample value at the specified row and column.
308: */
309: public Object getValueAt(int y, int x) {
310: final Raster raster = getRasterAt(y += minY, x += minX);
311: if (raster == null) {
312: return null;
313: }
314: switch (dataType) {
315: default:
316: return new Integer(raster.getSample(x, y, band));
317: case DataBuffer.TYPE_FLOAT:
318: return new Float(raster.getSampleFloat(x, y, band));
319: case DataBuffer.TYPE_DOUBLE:
320: return new Double(raster.getSampleDouble(x, y, band));
321: }
322: }
323:
324: /**
325: * Returns the color at the specified row and column.
326: */
327: public Color getColorAt(int y, int x) {
328: final Raster raster = getRasterAt(y += minY, x += minX);
329: if (raster == null) {
330: return null;
331: }
332: pixel = raster.getDataElements(x, y, pixel);
333: return new Color(image.getColorModel().getRGB(pixel), true);
334: }
335:
336: /**
337: * A table model for row headers. This model has only one column, and each cell values
338: * is the {@linkplain ImageTableModel#getRowName row name} defined in the enclosing class.
339: * A table using this model can be set as the
340: * {@linkplain javax.swing.JScrollPane#setRowHeaderView scroll pane's row header} for an
341: * image table.
342: *
343: * @since 2.2
344: * @version $Id: ImageTableModel.java 20883 2006-08-07 13:48:09Z jgarnett $
345: * @author Martin Desruisseaux
346: *
347: * @see javax.swing.JScrollPane#setRowHeader
348: */
349: public class RowHeaders extends AbstractTableModel implements
350: TableModelListener {
351: /**
352: * Serial number for compatibility with different versions.
353: */
354: private static final long serialVersionUID = 5162324745024331522L;
355:
356: /**
357: * Creates a new instance of row headers. This constructor immediately register
358: * the new instance as a listener of the enclosing {@link ImageTableModel}.
359: */
360: public RowHeaders() {
361: ImageTableModel.this .addTableModelListener(this );
362: }
363:
364: /**
365: * Returns the number of rows in the model. This is identical to
366: * the number of rows in the enclosing {@link ImageTableModel}.
367: */
368: public int getRowCount() {
369: return ImageTableModel.this .getRowCount();
370: }
371:
372: /**
373: * Returns the number of columns in the model, which is 1.
374: */
375: public int getColumnCount() {
376: return 1;
377: }
378:
379: /**
380: * Returns the type of row headers, which is {@code String.class}.
381: */
382: public Class getColumnClass(final int column) {
383: return String.class;
384: }
385:
386: /**
387: * Returns the row name for the given index, regardless of the column.
388: */
389: public Object getValueAt(final int row, final int column) {
390: return getRowName(row);
391: }
392:
393: /**
394: * Invoked when the enclosing {@link ImageTableModel} data changed. This method fires
395: * an event for this model as well except if the change was not a change in the table
396: * structure.
397: */
398: public void tableChanged(final TableModelEvent event) {
399: final int firstRow = event.getFirstRow();
400: final int lastRow = event.getLastRow();
401: final int type = event.getType();
402: if (type != TableModelEvent.UPDATE
403: || lastRow == Integer.MAX_VALUE) {
404: fireTableChanged(new TableModelEvent(this , firstRow,
405: lastRow, 0, type));
406: }
407: }
408: }
409: }
|