https://stackoverflow.com/questions/41728277/how-to-save-a-pixel-array-with-more-than-4-bands-to-a-tiff-geotiff-file-in-java
Suppose I manually created pixel values for more than 4 bands and I want to store them in a tiff file.
Those bands can be for R, G, B, temperature (values of temperature are not in the range of 0 to 255 thus I am using int instead of byte for pexils), etc. i.e. any information that can be taken from the satellite
Now I want to save those pixels to a tiff file. In java there is a BufferedImage class which has many types like: TYPE_4BYTE_ABGR, TYPE_BYTE_GRAY, etc. However none of them for multi bands more than 4 bands. There is TYPE_CUSTOM but when specifying it and trying to save the data to a Tiff file it gives you an exception because it is not supported for write operation (only for read operation i.e. it can read the file and set the type to TYPE_CUSTOM if it didn't understand the type but it can not write the file in a not understood type).
The below code worked for 3 bands even not properly (it doesn't show colored image and it looked like a distorted image with missing lines) but for more than 4 bands how can I do that?
ImageOutputStream ios = ImageIO.createImageOutputStream(os); Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("tiff"); ImageWriter writer = writers.next(); writer.setOutput(ios); int index = 0; int[] pixels = new int[width*height*numberOfBands]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { for (int k = 0; k < numberOfBands; k++) { pixels[index++] = //any values; } } } DataBuffer dataBuffer = new DataBufferInt(pixels, pixels.length); // Create Raster WritableRaster writableRaster = Raster.createBandedRaster (dataBuffer, width, height, width, // scanlineStride new int[numberOfBands], // bankIndices, new int[numberOfBands], // bandOffsets, null); // location // Create the image BufferedImage bufferedImage = new BufferedImage (width, height, BufferedImage.TYPE_BYTE_RGB); bufferedImage.setData(writableRaster); IIOImage iioImage = new IIOImage(bufferedImage, null, null); ImageWriteParam param = writer.getDefaultWriteParam(); writer.write(null, iioImage, param);
I am using GeoTools by the way
Edited: According to @iant I changed the code but it is giving blank transparent background only, even I kept the same number of bands i.e. 3 bands. @iant Could you check the code below.
package examples; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import javax.media.jai.RasterFactory; import org.geotools.coverage.CoverageFactoryFinder; import org.geotools.coverage.grid.GridCoordinates2D; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.grid.GridEnvelope2D; import org.geotools.coverage.grid.GridGeometry2D; import org.geotools.coverage.grid.io.AbstractGridFormat; import org.geotools.coverage.grid.io.OverviewPolicy; import org.geotools.gce.geotiff.GeoTiffFormat; import org.geotools.gce.geotiff.GeoTiffReader; import org.opengis.coverage.grid.GridCoverageWriter; import org.opengis.parameter.GeneralParameterValue; import org.opengis.parameter.ParameterValue; public class CreateTiffImageTest2 { public static void main(String[] args) throws IOException { File file = new File("/home/mosab/Desktop/input/tif.tif"); ParameterValue<OverviewPolicy> policy = AbstractGridFormat.OVERVIEW_POLICY.createValue(); policy.setValue(OverviewPolicy.IGNORE); ParameterValue<String> gridsize = AbstractGridFormat.SUGGESTED_TILE_SIZE.createValue(); ParameterValue<Boolean> useJaiRead = AbstractGridFormat.USE_JAI_IMAGEREAD.createValue(); useJaiRead.setValue(true); GeoTiffReader geoTiffReader = new GeoTiffReader(file); GridCoverage2D cov = geoTiffReader.read(new GeneralParameterValue[] { policy, gridsize, useJaiRead }); GridGeometry2D geometry = cov.getGridGeometry(); GridEnvelope2D gridEnvelope = geometry.getGridRange2D(); int w = (int) gridEnvelope.getWidth(); int h = (int) gridEnvelope.getHeight(); WritableRaster writableRaster = RasterFactory.createBandedRaster(java.awt.image.DataBuffer.TYPE_DOUBLE, w, h, 3, null); double[] data = new double[3]; double[] dest = new double[3]; for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { GridCoordinates2D coord = new GridCoordinates2D(i, j); cov.evaluate(coord, dest); data[0] = dest[0]; data[1] = dest[1]; data[2] = dest[2]; writableRaster.setPixel(i, j, data); } float perc = 100.0f * i / w; if (i % 100 == 0) { System.out.println("done " + perc); } } // Wrap the raster as a Coverage GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null); GridCoverage2D gc = factory.create("name", writableRaster, cov.getEnvelope()); File out = new File("/home/mosab/Desktop/input/tifgen.tif"); GeoTiffFormat format = new GeoTiffFormat(); GridCoverageWriter writer = format.getWriter(out); try { writer.write(gc, null); writer.dispose(); } catch (IllegalArgumentException | IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
}
Update 2:
import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.media.jai.RasterFactory; import org.geotools.coverage.CoverageFactoryFinder; import org.geotools.coverage.grid.GridCoverage2D; import org.geotools.coverage.grid.GridCoverageFactory; import org.geotools.coverage.grid.io.AbstractGridFormat; import org.geotools.coverage.grid.io.GridFormatFinder; import org.geotools.factory.Hints; import org.geotools.gce.geotiff.GeoTiffFormat; import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.referencing.CRS; import org.opengis.coverage.grid.GridCoverageWriter; import org.opengis.geometry.MismatchedDimensionException; import org.opengis.referencing.FactoryException; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.referencing.crs.CoordinateReferenceSystem; public class Test2 { public static void print(Object o) { System.out.println(o); } public static void main(String[] args) throws MismatchedDimensionException, NoSuchAuthorityCodeException, FactoryException, IOException { File out = new File("/home/mosab/Desktop/input/1.tif"); BufferedImage img = ImageIO.read(out); // ColorModel colorModel = img.getColorModel( WritableRaster raster = img.getRaster(); int w = img.getWidth(); int h = img.getHeight(); print("width = " + w); print("heigh = " + h); int numBands = raster.getNumBands(); WritableRaster writableRaster = RasterFactory.createBandedRaster(java.awt.image.DataBuffer.TYPE_INT, w, h, 3, null); //as I said pixels are created manually but I used here pixels from an image to check the approach int[] data = new int[3]; for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) { for (int k = 0; k < numBands; k++) { data[k] = raster.getSample(i, j, k); } writableRaster.setPixel(i, j, data); } } GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null); CoordinateReferenceSystem crs = CRS.decode("EPSG:27700"); int llx = 500000; int lly = 105000; ReferencedEnvelope referencedEnvelope = new ReferencedEnvelope(llx, llx + (w * 10), lly, lly + (h * 10), crs); GridCoverage2D gc = factory.create("name", writableRaster, referencedEnvelope); AbstractGridFormat format = GridFormatFinder.findFormat(out); Hints hints = null; if (format instanceof GeoTiffFormat) { hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE); } File out1 = new File("/home/mosab/Desktop/input/tifgen.tif"); GridCoverageWriter writer = format.getWriter(out1); try { writer.write(gc, null); writer.dispose(); } catch (IllegalArgumentException | IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
}
Notice: I used int type and an array of size 3 because the original image has RGB bands.
if you have such special demands you might consider having your own file format. –Piglet. Jan 19, 2017 at 9:12
I would work on getting it to work with 3 bands before trying 4 –Ian Turton.Jan 19, 2017 at 16:10
@Piglet it can be done using the TIff/Geotiff format, because it is intended for that. And by the way I mentioned earlier more than 4 bands, because in case of 4 some software will consider it ARGB i.e. +aplha . –Mosab Shaheen.Jan 21, 2017 at 9:48
@iant The problem here I am not able to find a type for BufferedImage to support more than 4 bands –Mosab Shaheen.Jan 21, 2017 at 9:50
that is why you need to use a writable raster which can have as many as you need. GeoTiff is technically limited to INT_MAX. –Ian Turton.Jan 21, 2017 at 11:19
1 Answer
You need to create a WritableRaster
using the other factory method which allows you to set the required data type and number of bands.
WritableRaster writableRaster = RasterFactory.createBandedRaster (java.awt.image.DataBuffer.TYPE_DOUBLE,width,height,4,null); double[] data = new double[4]; double[] dest = new double[3]; for(int i=0;i<width;i++) { for(int j=0;j<height;j++) { //basically anything you like to create the bands GridCoordinates2D coord = new GridCoordinates2D(i, j); //here I just grab the values of my base image and add them together cov.evaluate(coord, dest); data[0]=dest[0]; data[1] = dest[1]; data[2] = dest[2]; data[3] = (dest[0]+dest[1]+dest[2]); // write them to the new raster writableRaster.setPixel(i, j, data); } float perc = 100.0f*i/width; if(i%100==0) { System.out.println("done "+perc); } } //Wrap the raster as a Coverage GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null); GridCoverage2D gc = factory.create("name", writableRaster, cov.getEnvelope()); //write it out File out = new File(outFile); GridCoverageWriter writer = format.getWriter(out); try { writer.write(gc , null); writer.dispose(); } catch (IllegalArgumentException | IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
UPDATE
If you take the following code:
public void makeTestRaster() throws MismatchedDimensionException, NoSuchAuthorityCodeException, FactoryException { int width = 1000; int height = 1000; WritableRaster writableRaster = RasterFactory.createBandedRaster(java.awt.image.DataBuffer.TYPE_DOUBLE, width, height, 4, null); double[] data = new double[4]; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { data[0] = i * 100.0; data[1] = j * 100.0; data[2] = (width - i) * 100.0; data[3] = (height - j) * 100.0; System.out.println(i + "," + j + ":" + data[0] + " " + data[1] + " " + data[2] + " " + data[3] + " "); writableRaster.setPixel(i, j, data); } float perc = 100.0f * i / width; if (i % 100 == 0) { System.out.println("done " + perc); } } File out = new File("test.tif"); GridCoverageFactory factory = CoverageFactoryFinder.getGridCoverageFactory(null); CoordinateReferenceSystem crs = CRS.decode("EPSG:27700"); int llx = 500000; int lly = 105000; ReferencedEnvelope referencedEnvelope = new ReferencedEnvelope(llx, llx + (width * 10), lly, lly + (height * 10), crs); GridCoverage2D gc = factory.create("name", writableRaster, referencedEnvelope); AbstractGridFormat format = GridFormatFinder.findFormat(out); Hints hints = null; if (format instanceof GeoTiffFormat) { hints = new Hints(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER, Boolean.TRUE); } GridCoverageWriter writer = format.getWriter(out); try { writer.write(gc, null); writer.dispose(); } catch (IllegalArgumentException | IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
It creates a 10 KM square raster (near my house). It has 4 bands which could represent anything you like, running gdalinfo
on it gives the following information:
gdalinfo test.tif Driver: GTiff/GeoTIFF Files: test.tif test.tif.aux.xml Size is 1000, 1000 Coordinate System is: PROJCS["OSGB 1936 / British National Grid", GEOGCS["OSGB 1936", DATUM["OSGB_1936", SPHEROID["Airy 1830",6377563.396,299.3249646, AUTHORITY["EPSG","7001"]], TOWGS84[446.448,-125.157,542.06,0.15,0.247,0.842,-20.489], AUTHORITY["EPSG","6277"]], PRIMEM["Greenwich",0, AUTHORITY["EPSG","8901"]], UNIT["degree",0.0174532925199433, AUTHORITY["EPSG","9122"]], AUTHORITY["EPSG","4277"]], PROJECTION["Transverse_Mercator"], PARAMETER["latitude_of_origin",49], PARAMETER["central_meridian",-2], PARAMETER["scale_factor",0.9996012717], PARAMETER["false_easting",400000], PARAMETER["false_northing",-100000], UNIT["metre",1, AUTHORITY["EPSG","9001"]], AXIS["Easting",EAST], AXIS["Northing",NORTH], AUTHORITY["EPSG","27700"]] Origin = (500000.000000000000000,115000.000000000000000) Pixel Size = (10.000000000000000,-10.000000000000000) Metadata: AREA_OR_POINT=Area TIFFTAG_RESOLUTIONUNIT=1 (unitless) TIFFTAG_XRESOLUTION=1 TIFFTAG_YRESOLUTION=1 Image Structure Metadata: INTERLEAVE=PIXEL Corner Coordinates: Upper Left ( 500000.000, 115000.000) ( 0d34'37.20"W, 50d55'30.82"N) Lower Left ( 500000.000, 105000.000) ( 0d34'47.05"W, 50d50' 7.16"N) Upper Right ( 510000.000, 115000.000) ( 0d26' 5.12"W, 50d55'24.27"N) Lower Right ( 510000.000, 105000.000) ( 0d26'15.95"W, 50d50' 0.62"N) Center ( 505000.000, 110000.000) ( 0d30'26.33"W, 50d52'45.79"N) Band 1 Block=1000x8 Type=Float64, ColorInterp=Gray Min=0.000 Max=99900.000 Minimum=0.000, Maximum=99900.000, Mean=49950.000, StdDev=28867.499 Metadata: STATISTICS_MAXIMUM=99900 STATISTICS_MEAN=49950 STATISTICS_MINIMUM=0 STATISTICS_STDDEV=28867.499025721 Band 2 Block=1000x8 Type=Float64, ColorInterp=Undefined Min=0.000 Max=97500.000 Minimum=0.000, Maximum=97500.000, Mean=48750.000, StdDev=30378.926 Metadata: STATISTICS_MAXIMUM=97500 STATISTICS_MEAN=48750 STATISTICS_MINIMUM=0 STATISTICS_STDDEV=30378.926358031 Band 3 Block=1000x8 Type=Float64, ColorInterp=Undefined Min=0.000 Max=97402500.000 Minimum=0.000, Maximum=97402500.000, Mean=24350625.000, StdDev=22476916.605 Metadata: STATISTICS_MAXIMUM=97402500 STATISTICS_MEAN=24350625 STATISTICS_MINIMUM=0 STATISTICS_STDDEV=22476916.605084 Band 4 Block=1000x8 Type=Float64, ColorInterp=Undefined Min=2500.000 Max=100000.000 Minimum=2500.000, Maximum=100000.000, Mean=51250.000, StdDev=30378.926 Metadata: STATISTICS_MAXIMUM=100000 STATISTICS_MEAN=51249.999999999 STATISTICS_MINIMUM=2500 STATISTICS_STDDEV=30378.926358031
Dear, in the code above there is the "cov" variable and you used it two times, first time in the origional image you took the pixels from: cov.evaluate(coord, dest); and second time in the generated image: GridCoverage2D gc = factory.create("name", writableRaster, cov.getEnvelope()); –
Mosab Shaheen
Jan 21, 2017 at 14:34
but as I said before I don't want to depend on the original image I want to set the pixels "manually" as if there is no original image before. So I need the code to either create a separate "cov" variable, not depending on the original one, or trying different approach. So Could you rewrite the code to reflect that. –
Mosab Shaheen
Jan 21, 2017 at 14:35
you can set your pixels any way you want, I happened to copy mine so I could check the output image was correct. You presumably know what the envelope of your output is so you can use that. –
Ian Turton
Jan 21, 2017 at 14:37
Thanks I am trying it is giving me a blank transparent background only. Could you check the complete code I added above in the "Edit Post" –
Mosab Shaheen
Jan 21, 2017 at 14:55
The code is ready to execute just run it and tell me please why it is giving only a blank transparent background( or if you code post the full code of yours to try it) –
Mosab Shaheen
Jan 21, 2017 at 14:57