All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.util.ImageTiler Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2012 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.util;

import gov.nasa.worldwind.geom.*;

import java.awt.*;
import java.awt.image.*;
import java.util.*;
import java.util.List;
import java.util.logging.Level;

/**
 * Subdivides an image into tiles and computes the corresponding sectors. The width and height of the returned tiles can
 * be specified but default to 1024. If the base image width or height is not evenly divisible by the corresponding
 * desired tile dimension, tiles along the right and bottom of the base image may contain pixels that do not correspond
 * to pixels in the image. These pixels will have an alpha component of 0 and the corresponding tile will have an alpha
 * channel. Otherwise tiles will not have an alpha channel. If the input image is already the desired subimage size, it
 * is returned without being copied.
 *
 * @author tag
 * @version $Id: ImageTiler.java 1171 2013-02-11 21:45:02Z dcollins $
 */
public class ImageTiler
{
    public static int DEFAULT_IMAGE_TILE_SIZE = 2048; // default size to make subimages

    private int tileWidth = DEFAULT_IMAGE_TILE_SIZE;
    private int tileHeight = DEFAULT_IMAGE_TILE_SIZE;
    private Color transparencyColor = new Color(0, 0, 0, 0);

    public int getTileWidth()
    {
        return tileWidth;
    }

    public void setTileWidth(int tileWidth)
    {
        this.tileWidth = tileWidth;
    }

    public int getTileHeight()
    {
        return tileHeight;
    }

    public void setTileHeight(int tileHeight)
    {
        this.tileHeight = tileHeight;
    }

    public Color getTransparencyColor()
    {
        return transparencyColor;
    }

    public void setTransparencyColor(Color transparencyColor)
    {
        this.transparencyColor = transparencyColor;
    }

    /**
     * Performs a subdivision according to the current parameters and assuming that the image corresponds with a {@link
     * Sector} rather than a quadrilateral or other shape. Conveys each tile created to the caller via a listener
     * callback.
     *
     * @param baseImage  the image to tile.
     * @param baseSector the sector defining the geographic extent of the base image.
     * @param listener   the listener to invoke when each new tile is created.
     *
     * @see ImageTilerListener
     */
    public void tileImage(BufferedImage baseImage, Sector baseSector, ImageTilerListener listener)
    {
        if (baseImage == null)
        {
            String message = Logging.getMessage("nullValue.ImageSource");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (baseSector == null)
        {
            String message = Logging.getMessage("nullValue.SectorIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (baseImage.getWidth() <= 0 || baseImage.getHeight() <= 0)
        {
            String message = Logging.getMessage("generic.InvalidImageSize");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (listener == null)
        {
            String message = Logging.getMessage("nullValue.ListenerIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // Just return the input image if it's already the desired subimage size
        if (baseImage.getWidth() == this.getTileWidth() && baseImage.getHeight() == this.getTileHeight())
        {
            listener.newTile(baseImage, baseSector);
            return;
        }

        int M = baseImage.getWidth();
        int N = baseImage.getHeight();
        int a = Math.min(M, this.getTileWidth());
        int b = Math.min(N, this.getTileHeight());
        int cols = (int) Math.ceil((double) M / a);
        int rows = (int) Math.ceil((double) N / b);
        boolean hasAlpha = baseImage.getColorModel().hasAlpha();

        for (int j = 0; j < rows; j++)
        {
            int y = j * b;
            int h = y + b <= N ? b : N - y;

            double t0 = (double) (y + this.getTileHeight()) / N;
            double t1 = (double) y / N;
            Angle minLat = baseSector.getMaxLatitude().subtract(baseSector.getDeltaLat().multiply(t0));
            Angle maxLat = baseSector.getMaxLatitude().subtract(baseSector.getDeltaLat().multiply(t1));

            for (int i = 0; i < cols; i++)
            {
                int x = i * a;
                int w = x + a <= M ? a : M - x;

                BufferedImage image;
                if (w == this.getTileWidth() && h == this.getTileHeight())
                {
                    // The source image fills this tile entirely,
                    if (!hasAlpha)
                    {
                        // If the source image does not have an alpha channel, create a tile with no alpha channel.
                        image = new BufferedImage(this.getTileWidth(), this.getTileHeight(),
                            BufferedImage.TYPE_3BYTE_BGR);
                        if (!ImageUtil.isCompatibleImage(image))
                            image = ImageUtil.toCompatibleImage(image);
                        Graphics2D g = image.createGraphics();
                        g.drawImage(baseImage.getSubimage(x, y, w, h), 0, 0, w, h, null);
                    }
                    else
                    {
                        // The source image has an alpha channel, create a tile with an alpha channel.
                        image = new BufferedImage(this.getTileWidth(), this.getTileHeight(),
                            BufferedImage.TYPE_4BYTE_ABGR);
                        if (!ImageUtil.isCompatibleImage(image))
                            image = ImageUtil.toCompatibleImage(image);
                        Graphics2D g = image.createGraphics();
                        g.setBackground(this.transparencyColor);
                        g.clearRect(0, 0, image.getWidth(), image.getHeight());
                        g.drawImage(baseImage.getSubimage(x, y, w, h), 0, 0, w, h, null);
                    }

                    // Compute the sector for this tile
                    double s0 = (double) x / M;
                    double s1 = ((double) x + this.getTileWidth()) / M;
                    Angle minLon = baseSector.getMinLongitude().add(baseSector.getDeltaLon().multiply(s0));
                    Angle maxLon = baseSector.getMinLongitude().add(baseSector.getDeltaLon().multiply(s1));

//                    System.out.println(new Sector(minLat, maxLat, minLon, maxLon));
                    listener.newTile(image, new Sector(minLat, maxLat, minLon, maxLon));
                }
                else
                {
                    // The source image does not fill this tile, so create a smaller tile with an alpha channel.
                    int shortWidth = w == this.getTileWidth() ? this.getTileWidth() : WWMath.powerOfTwoCeiling(w);
                    int shortheight = h == this.getTileHeight() ? this.getTileHeight() : WWMath.powerOfTwoCeiling(h);

                    image = new BufferedImage(shortWidth, shortheight, BufferedImage.TYPE_4BYTE_ABGR);
                    if (!ImageUtil.isCompatibleImage(image))
                        image = ImageUtil.toCompatibleImage(image);
                    Graphics2D g = image.createGraphics();
                    g.setBackground(this.transparencyColor);
                    g.clearRect(0, 0, image.getWidth(), image.getHeight());
                    g.drawImage(baseImage.getSubimage(x, y, w, h), 0, 0, w, h, null);

                    // Compute the sector for this tile
                    double s0 = (double) x / M;
                    double s1 = ((double) x + image.getWidth()) / M;
                    Angle minLon = baseSector.getMinLongitude().add(baseSector.getDeltaLon().multiply(s0));
                    Angle maxLon = baseSector.getMinLongitude().add(baseSector.getDeltaLon().multiply(s1));

                    // Must recalculate t0 to account for short tile height.
                    double t00 = (double) (y + image.getHeight()) / N;
                    Angle minLat0 = baseSector.getMaxLatitude().subtract(baseSector.getDeltaLat().multiply(t00));

//                    System.out.println(new Sector(minLat0, maxLat, minLon, maxLon));
                    listener.newTile(image, new Sector(minLat0, maxLat, minLon, maxLon));
                }
            }
        }
    }

    public void tileImage(BufferedImage image, java.util.List corners,ImageTilerListener listener)
    {
        if (image == null)
        {
            String message = Logging.getMessage("nullValue.ImageSource");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (corners == null)
        {
            String message = Logging.getMessage("nullValue.LocationsListIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (image.getWidth() <= 0 || image.getHeight() <= 0)
        {
            String message = Logging.getMessage("generic.InvalidImageSize");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        if (listener == null)
        {
            String message = Logging.getMessage("nullValue.ListenerIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        // Just return the input image if it's already the desired subimage size
        if (image.getWidth() == this.getTileWidth() && image.getHeight() == this.getTileHeight())
        {
            listener.newTile(image, corners);
            return;
        }

        // Count the corners and check for nulls
        int numCorners = 0;
        for (LatLon c : corners)
        {
            if (c == null)
            {
                String message = Logging.getMessage("nullValue.LocationInListIsNull");
                Logging.logger().log(Level.SEVERE, message);
                throw new IllegalArgumentException(message);
            }

            if (++numCorners > 3)
                break;
        }

        if (numCorners < 4)
        {
            String message = Logging.getMessage("nullValue.LocationInListIsNull");
            Logging.logger().severe(message);
            throw new IllegalArgumentException(message);
        }

        GeoQuad geoQuad = new GeoQuad(corners);

        int M = image.getWidth();
        int N = image.getHeight();
        int a = Math.min(M, this.getTileWidth());
        int b = Math.min(N, this.getTileHeight());
        int cols = (int) Math.ceil((double) M / a);
        int rows = (int) Math.ceil((double) N / b);
        boolean hasAlpha = image.getColorModel().hasAlpha();

        for (int j = 0; j < rows; j++)
        {
            LatLon se, sw, ne, nw;

            int y = j * b;
            int h = y + b <= N ? b : N - y;

            double t0 = 1d - (double) (y + this.getTileHeight()) / N;
            double t1 = 1d - (double) y / N;

            for (int i = 0; i < cols; i++)
            {
                int x = i * a;
                int w = x + a <= M ? a : M - x;

                BufferedImage subImage;
                if (w == this.getTileWidth() && h == this.getTileHeight())
                {

                    // The source image fills this tile entirely,
                    if (!hasAlpha)
                    {
                        // If the source image does not have an alpha channel, create a tile with no alpha channel.
                        subImage = new BufferedImage(this.getTileWidth(), this.getTileHeight(),
                            BufferedImage.TYPE_3BYTE_BGR);
                        Graphics2D g = subImage.createGraphics();
                        g.drawImage(image.getSubimage(x, y, w, h), 0, 0, w, h, null);
                        
                        continue;
                    }
                    else
                    {
                        // The source image has an alpha channel, create a tile with an alpha channel.
                        subImage = new BufferedImage(this.getTileWidth(), this.getTileHeight(),
                            BufferedImage.TYPE_4BYTE_ABGR);
                        Graphics2D g = subImage.createGraphics();
                        g.setBackground(this.transparencyColor);
                        g.clearRect(0, 0, subImage.getWidth(), subImage.getHeight());
                        g.drawImage(image.getSubimage(x, y, w, h), 0, 0, w, h, null);
                    }

                    // Compute the sector for this tile
                    double s0 = (double) x / M;
                    double s1 = ((double) x + this.getTileWidth()) / M;

                    sw = geoQuad.interpolate(t0, s0);
                    se = geoQuad.interpolate(t0, s1);
                    ne = geoQuad.interpolate(t1, s1);
                    nw = geoQuad.interpolate(t1, s0);
                }
                else
                {
                    // The source image does not fill this tile, so create a smaller tile with an alpha channel.
                    int shortWidth = w == this.getTileWidth() ? this.getTileWidth() : WWMath.powerOfTwoCeiling(w);
                    int shortheight = h == this.getTileHeight() ? this.getTileHeight() : WWMath.powerOfTwoCeiling(h);

                    subImage = new BufferedImage(shortWidth, shortheight, BufferedImage.TYPE_4BYTE_ABGR);
                    Graphics2D g = subImage.createGraphics();
                    g.setBackground(this.transparencyColor);
                    g.clearRect(0, 0, subImage.getWidth(), subImage.getHeight());
                    g.drawImage(image.getSubimage(x, y, w, h), 0, 0, w, h, null);

                    // Compute the sector for this tile
                    double s0 = (double) x / M;
                    double s1 = ((double) x + subImage.getWidth()) / M;

                    // Must recalculate t0 to account for short tile height.
                    double t0b = 1d - (double) (y + subImage.getHeight()) / N;

                    sw = geoQuad.interpolate(t0b, s0);
                    se = geoQuad.interpolate(t0b, s1);
                    ne = geoQuad.interpolate(t1, s1);
                    nw = geoQuad.interpolate(t1, s0);
                }

//                System.out.printf("%d: (%d, %d) : SW %s; SE %s; NE %s; NW %s\n",
//                    System.currentTimeMillis(), x, y, sw, se, ne, nw);

                listener.newTile(subImage, Arrays.asList(sw,se, ne, nw));
            }
        }
    }

    public abstract static class ImageTilerListener
    {
        public abstract void newTile(BufferedImage tileImage, Sector tileSector);

        public abstract void newTile(BufferedImage tileImage, List corners);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy