content: /*
   * Copyright (C) 2019 Adaptech s.r.o., Robert Pösel
   * Copyright (C) 2011 Google Inc.
   *
   * Licensed under the Apache License, Version 2.0 (the "License"); you may not
   * use this file except in compliance with the License. You may obtain a copy of
   * the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   * License for the specific language governing permissions and limitations under
   * the License.
   */
  
  package com.googlecode.leptonica.android;
  
  import android.graphics.Rect;
  import android.util.Log;
  
  import java.io.File;
  import java.util.ArrayList;
  import java.util.Iterator;
  
  import androidx.annotation.Size;
  
  /**
   * Java representation of a native PIXA object. This object contains multiple
   * PIX objects and their associated bounding BOX objects.
   *
   * @author alanv@google.com (Alan Viverette)
   */
  @SuppressWarnings("WeakerAccess")
  public class Pixa implements Iterable<Pix> {
  	static {
  		System.loadLibrary("jpeg");
  		System.loadLibrary("pngx");
  		System.loadLibrary("leptonica");
  	}
  
  	private static final String TAG = Pixa.class.getSimpleName();
  
  	/**
  	 * A pointer to the native PIXA object. This is used internally by native code.
  	 */
  	private final long mNativePixa;
  
  	/**
  	 * The specified width of this Pixa.
  	 */
  	final int mWidth;
  
  	/**
  	 * The specified height of this Pixa.
  	 */
  	final int mHeight;
  
  	private boolean mRecycled;
  
  	/**
  	 * Creates a new Pixa with the specified minimum capacity. The Pixa will
  	 * expand automatically as new Pix are added.
  	 *
  	 * @param size The minimum capacity of this Pixa.
  	 * @return a new Pixa or <code>null</code> on error
  	 */
  	public static Pixa createPixa(int size) {
  		return createPixa(size, 0, 0);
  	}
  
  	/**
  	 * Creates a new Pixa with the specified minimum capacity. The Pixa will
  	 * expand automatically as new Pix are added.
  	 * <p>
  	 * If non-zero, the specified width and height will be used to specify the
  	 * bounds of output images. *
  	 *
  	 * @param size   The minimum capacity of this Pixa.
  	 * @param width  (Optional) The width of this Pixa, use 0 for default.
  	 * @param height (Optional) The height of this Pixa, use 0 for default.
  	 * @return a new Pixa or <code>null</code> on error
  	 */
  	public static Pixa createPixa(int size, int width, int height) {
  		long nativePixa = nativeCreate(size);
  
  		if (nativePixa == 0) {
  			throw new OutOfMemoryError();
  		}
  
  		return new Pixa(nativePixa, width, height);
  	}
  
  	/**
  	 * Creates a wrapper for the specified native Pixa pointer.
  	 *
  	 * @param nativePixa Native pointer to a PIXA object.
  	 * @param width      The width of the PIXA.
  	 * @param height     The height of the PIXA.
  	 */
  	public Pixa(long nativePixa, int width, int height) {
  		mNativePixa = nativePixa;
  		mWidth = width;
  		mHeight = height;
  		mRecycled = false;
  	}
  
  	/**
  	 * Returns a pointer to the native PIXA object. This is used by native code.
  	 *
  	 * @return a pointer to the native PIXA object
  	 */
  	public long getNativePixa() {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		return mNativePixa;
  	}
  
  	/**
  	 * Creates a shallow copy of this Pixa. Contained Pix are cloned, and the
  	 * resulting Pixa may be recycled separately from the original.
  	 *
  	 * @return a shallow copy of this Pixa
  	 */
  	public Pixa copy() {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		long nativePixa = nativeCopy(mNativePixa);
  
  		if (nativePixa == 0) {
  			throw new OutOfMemoryError();
  		}
  
  		return new Pixa(nativePixa, mWidth, mHeight);
  	}
  
  	/**
  	 * Sorts this Pixa using the specified field and order. See
  	 * Constants.L_SORT_BY_* and Constants.L_SORT_INCREASING or
  	 * Constants.L_SORT_DECREASING.
  	 *
  	 * @param field The field to sort by. See Constants.L_SORT_BY_*.
  	 * @param order The order in which to sort. Must be either
  	 *              Constants.L_SORT_INCREASING or Constants.L_SORT_DECREASING.
  	 * @return a sorted copy of this Pixa
  	 */
  	public Pixa sort(@Constants.SortBy int field, @Constants.SortOrder int order) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		long nativePixa = nativeSort(mNativePixa, field, order);
  
  		if (nativePixa == 0) {
  			throw new OutOfMemoryError();
  		}
  
  		return new Pixa(nativePixa, mWidth, mHeight);
  	}
  
  	/**
  	 * Returns the number of elements in this Pixa.
  	 *
  	 * @return the number of elements in this Pixa
  	 */
  	public int size() {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		return nativeGetCount(mNativePixa);
  	}
  
  	/**
  	 * Recycles this Pixa and frees natively allocated memory. You may not
  	 * access or modify the Pixa after calling this method.
  	 * <p>
  	 * Any Pix obtained from this Pixa or copies of this Pixa will still be
  	 * accessible until they are explicitly recycled or finalized by the garbage
  	 * collector.
  	 */
  	public synchronized void recycle() {
  		if (!mRecycled) {
  			nativeDestroy(mNativePixa);
  
  			mRecycled = true;
  		}
  	}
  
  	@Override
  	protected void finalize() throws Throwable {
  		try {
  			if (!mRecycled) {
  				Log.w(TAG, "Pixa was not terminated using recycle()");
  				recycle();
  			}
  		} finally {
  			super.finalize();
  		}
  	}
  
  	/**
  	 * Merges the contents of another Pixa into this one.
  	 *
  	 * @param otherPixa The Pix to merge.
  	 * @return <code>true</code> on success
  	 */
  	public boolean join(Pixa otherPixa) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		return nativeJoin(mNativePixa, otherPixa.mNativePixa);
  	}
  
  	/**
  	 * Adds a Pix to this Pixa.
  	 *
  	 * @param pix  The Pix to add.
  	 * @param mode The mode in which to add this Pix, typically
  	 *             Constants.L_CLONE.
  	 */
  	public void addPix(Pix pix, @Constants.StorageFlag int mode) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		nativeAddPix(mNativePixa, pix.getNativePix(), mode);
  	}
  
  	/**
  	 * Adds a Box to this Pixa.
  	 *
  	 * @param box  The Box to add.
  	 * @param mode The mode in which to add this Box, typically
  	 *             Constants.L_CLONE.
  	 */
  	public void addBox(Box box, @Constants.StorageFlag int mode) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		nativeAddBox(mNativePixa, box.getNativeBox(), mode);
  	}
  
  	/**
  	 * Adds a Pix and associated Box to this Pixa.
  	 *
  	 * @param pix  The Pix to add.
  	 * @param box  The Box to add.
  	 * @param mode The mode in which to add this Pix and Box, typically
  	 *             Constants.L_CLONE.
  	 */
  	public void add(Pix pix, Box box, @Constants.StorageFlag int mode) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		nativeAdd(mNativePixa, pix.getNativePix(), box.getNativeBox(), mode);
  	}
  
  	/**
  	 * Creates and returns clone of the Box at the specified index, or <code>null</code> on error.
  	 * Returned object should be recycled after use.
  	 *
  	 * @param index The index of the Box to return.
  	 * @return the Box at the specified index, or <code>null</code> on error
  	 */
  	public Box getBox(int index) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		long nativeBox = nativeGetBox(mNativePixa, index);
  
  		if (nativeBox == 0) {
  			return null;
  		}
  
  		return new Box(nativeBox);
  	}
  
  	/**
  	 * Creates and returns clone of the Pix at the specified index, or <code>null</code> on error.
  	 * Returned object should be recycled after use.
  	 *
  	 * @param index The index of the Pix to return.
  	 * @return the Pix at the specified index, or <code>null</code> on error
  	 */
  	public Pix getPix(int index) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		long nativePix = nativeGetPix(mNativePixa, index);
  
  		if (nativePix == 0) {
  			return null;
  		}
  
  		return new Pix(nativePix);
  	}
  
  	/**
  	 * Returns the width of this Pixa, or 0 if one was not set when it was
  	 * created.
  	 *
  	 * @return the width of this Pixa, or 0 if one was not set when it was
  	 * created
  	 */
  	public int getWidth() {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		return mWidth;
  	}
  
  	/**
  	 * Returns the height of this Pixa, or 0 if one was not set when it was
  	 * created.
  	 *
  	 * @return the height of this Pixa, or 0 if one was not set when it was
  	 * created
  	 */
  	public int getHeight() {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		return mHeight;
  	}
  
  	/**
  	 * Returns a bounding Rect for this Pixa, which may be (0,0,0,0) if width
  	 * and height were not specified on creation.
  	 *
  	 * @return a bounding Rect for this Pixa
  	 */
  	public Rect getRect() {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		return new Rect(0, 0, mWidth, mHeight);
  	}
  
  	/**
  	 * Returns a geometry array for the Box at the specified index. See
  	 * Box.INDEX_* for indices.
  	 *
  	 * @param index The index of the Box to get the geometry of.
  	 * @return a bounding Rect for the Box at the specified index
  	 */
  	public int[] getBoxGeometry(int index) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		int[] dimensions = new int[4];
  
  		if (getBoxGeometry(index, dimensions)) {
  			return dimensions;
  		}
  
  		return null;
  	}
  
  	/**
  	 * Fills an array with the geometry of the Box at the specified index. See
  	 * Box.INDEX_* for indices.
  	 *
  	 * @param index      The index of the Box to get the geometry of.
  	 * @param dimensions The array to fill with Box geometry. Must be at least 4
  	 *                   elements.
  	 * @return <code>true</code> on success
  	 */
  	public boolean getBoxGeometry(int index, @Size(min = 4) int[] dimensions) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		return nativeGetBoxGeometry(mNativePixa, index, dimensions);
  	}
  
  	/**
  	 * Returns a bounding Rect for the Box at the specified index.
  	 *
  	 * @param index The index of the Box to get the bounding Rect of.
  	 * @return a bounding Rect for the Box at the specified index
  	 */
  	public Rect getBoxRect(int index) {
  		int[] dimensions = getBoxGeometry(index);
  
  		if (dimensions == null) {
  			return null;
  		}
  
  		int x = dimensions[Box.INDEX_X];
  		int y = dimensions[Box.INDEX_Y];
  		int w = dimensions[Box.INDEX_W];
  		int h = dimensions[Box.INDEX_H];
  
  		return new Rect(x, y, x + w, y + h);
  	}
  
  	/**
  	 * Returns an ArrayList of Box bounding Rects.
  	 *
  	 * @return an ArrayList of Box bounding Rects
  	 */
  	public ArrayList<Rect> getBoxRects() {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		final int pixaCount = nativeGetCount(mNativePixa);
  		final int[] buffer = new int[4];
  		final ArrayList<Rect> rects = new ArrayList<Rect>(pixaCount);
  
  		for (int i = 0; i < pixaCount; i++) {
  			getBoxGeometry(i, buffer);
  
  			final int x = buffer[Box.INDEX_X];
  			final int y = buffer[Box.INDEX_Y];
  			final Rect bound = new Rect(x, y, x + buffer[Box.INDEX_W], y + buffer[Box.INDEX_H]);
  
  			rects.add(bound);
  		}
  
  		return rects;
  	}
  
  	/**
  	 * Replaces the Pix and Box at the specified index with the specified Pix
  	 * and Box. Pixa takes ownership of the given objects, they shouldn't be
  	 * used or recycled after calling this method.
  	 *
  	 * @param index The index of the Pix to replace.
  	 * @param pix The Pix to replace the existing Pix; it becomes an alias of the one stored in Pixa.
  	 * @param box The Box to replace the existing Box; it becomes an alias of the one stored in Pixa.
  	 */
  	public void replacePix(int index, Pix pix, Box box) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		nativeReplacePix(mNativePixa, index, pix.getNativePix(),
  				box.getNativeBox());
  	}
  
  	/**
  	 * Merges the Pix at the specified indices and removes the Pix at the second
  	 * index.
  	 *
  	 * @param indexA The index of the first Pix.
  	 * @param indexB The index of the second Pix, which will be removed after
  	 *               merging.
  	 */
  	public void mergeAndReplacePix(int indexA, int indexB) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		nativeMergeAndReplacePix(mNativePixa, indexA, indexB);
  	}
  
  	/**
  	 * Writes the components of this Pix to a bitmap-formatted file using a
  	 * random color map.
  	 *
  	 * @param file The file to write to.
  	 * @return <code>true</code> on success
  	 */
  	public boolean writeToFileRandomCmap(File file) {
  		if (mRecycled)
  			throw new IllegalStateException();
  
  		return nativeWriteToFileRandomCmap(mNativePixa, file.getAbsolutePath(), mWidth, mHeight);
  	}
  
  	public Iterator<Pix> iterator() {
  		return new PixIterator();
  	}
  
  	private class PixIterator implements Iterator<Pix> {
  		private int mIndex;
  
  		private PixIterator() {
  			mIndex = 0;
  		}
  
  		public boolean hasNext() {
  			final int size = size();
  			return (size > 0 && mIndex < size);
  		}
  
  		public Pix next() {
  			return getPix(mIndex++);
  		}
  
  		public void remove() {
  			throw new UnsupportedOperationException();
  		}
  	}
  
  	// ***************
  	// * NATIVE CODE *
  	// ***************
  
  	private static native long nativeCreate(int size);
  
  	private static native long nativeCopy(long nativePixa);
  
  	private static native long nativeSort(long nativePixa, int field, int order);
  
  	private static native boolean nativeJoin(long nativePixa, long otherPixa);
  
  	private static native int nativeGetCount(long nativePixa);
  
  	private static native void nativeDestroy(long nativePixa);
  
  	private static native void nativeAddPix(long nativePixa, long nativePix, int mode);
  
  	private static native void nativeAddBox(long nativePixa, long nativeBox, int mode);
  
  	private static native void nativeAdd(long nativePixa, long nativePix, long nativeBox, int mode);
  
  	private static native boolean nativeWriteToFileRandomCmap(
  			long nativePixa, String fileName, int width, int height);
  
  	private static native void nativeReplacePix(
  			long nativePixa, int index, long nativePix, long nativeBox);
  
  	private static native void nativeMergeAndReplacePix(long nativePixa, int indexA, int indexB);
  
  	private static native long nativeGetBox(long nativePix, int index);
  
  	private static native long nativeGetPix(long nativePix, int index);
  
  	private static native boolean nativeGetBoxGeometry(long nativePixa, int index, int[] dimensions);
  }
  