diff --git a/project.clj b/project.clj index 4bd9542..2c362e7 100644 --- a/project.clj +++ b/project.clj @@ -13,6 +13,7 @@ [environ "0.4.0"] [clj-metasearch "0.1.1"] [clj-figlet "0.1.1"] + [clj-image2ascii "0.1.0-SNAPSHOT"] [com.cemerick/url "0.1.1"] [criterium "0.4.3" :scope "test"]] :source-paths ["src/clojure"] diff --git a/src/clojure/toascii/models/image.clj b/src/clojure/toascii/models/image.clj index 6c04bcb..cb23ab8 100644 --- a/src/clojure/toascii/models/image.clj +++ b/src/clojure/toascii/models/image.clj @@ -1,57 +1,19 @@ (ns toascii.models.image - (:import (java.awt RenderingHints Graphics2D Image) - (java.awt.image BufferedImage Raster) - (javax.imageio ImageIO) - (java.io File) - (java.net URL) - (toascii.images ImageToAscii)) - (:require [clojure.string :as str] - [cemerick.url :as url] - [toascii.util :refer [query-param-url->java-url]]) + (:import (java.awt.image BufferedImage)) + (:require [toascii.util :refer [query-param-url->java-url]] + [clj-image2ascii.core :as i2a]) (:use hiccup.core)) -(defn get-image-by-url - (^BufferedImage [^String url] - (try - (let [java-url (query-param-url->java-url url)] - (ImageIO/read java-url)) - (catch Exception ex)))) - -(defn get-image-by-file - "returns a BufferedImage loaded from the file specified, or null if an error occurs." - (^BufferedImage [^File file] - (try - (ImageIO/read file) - (catch Exception ex)))) - -(defn scale-image - "takes a source image specified by the uri (a filename or a URL) and scales it proportionally - using the new width, returning the newly scaled image." - (^BufferedImage [^BufferedImage image new-width] - (let [new-height (* (/ new-width (.getWidth image)) - (.getHeight image)) - scaled-image (BufferedImage. new-width new-height BufferedImage/TYPE_INT_RGB) - gfx2d (doto (.createGraphics scaled-image) - (.setRenderingHint RenderingHints/KEY_INTERPOLATION - RenderingHints/VALUE_INTERPOLATION_BILINEAR) - (.drawImage image 0 0 new-width new-height nil) - (.dispose))] - scaled-image))) - -(defn convert-image - "converts the image to an ascii representation. a multiline string will be returned, - which will be formatted for html if color? is true, or plain text if false. if - scale-to-width is specified the resulting image will be scaled proportionally from - the source image." - ([^BufferedImage image color?] - (convert-image image nil color?)) - ([^BufferedImage image scale-to-width color?] - (let [current-width (.getWidth image) - new-width (or scale-to-width current-width) - final-image (if-not (= new-width current-width) - (scale-image image new-width) - image)] - (ImageToAscii/convert final-image color?)))) +(defn get-image [^String url] + (let [java-url (query-param-url->java-url url)] + (i2a/get-image-by-url java-url))) (defn wrap-pre-tag [s] - (str "
" s "
")) \ No newline at end of file + (str "
" s "
")) + +(defn image->ascii [^BufferedImage image scale-to-width color? html?] + (let [converted (i2a/convert-image image scale-to-width color?) + ascii (:image converted)] + (if html? + (wrap-pre-tag ascii) + ascii))) diff --git a/src/clojure/toascii/routes/api/image.clj b/src/clojure/toascii/routes/api/image.clj index 1147ffc..c984cda 100644 --- a/src/clojure/toascii/routes/api/image.clj +++ b/src/clojure/toascii/routes/api/image.clj @@ -4,7 +4,7 @@ [liberator.core :refer [defresource]] [compojure.core :refer [ANY]] [toascii.route-utils :refer [register-routes]] - [toascii.models.image :refer [convert-image get-image-by-url wrap-pre-tag]] + [toascii.models.image :refer [image->ascii get-image]] [toascii.util :refer [parse-int parse-boolean]])) (defresource render-image [{:keys [url width color format] :as params}] @@ -26,21 +26,15 @@ (parse-boolean color)) {:error "Cannot output colored plain text version of image."})) :exists? (fn [_] - (if-let [image (get-image-by-url url)] + (if-let [image (get-image url)] {:image image} [false {:error "Image could not be loaded."}])) :handle-ok (fn [ctx] (let [html? (= "text/html" (get-in ctx [:representation :media-type])) color? (or (and html? (nil? color)) - (parse-boolean color)) - output (convert-image - (:image ctx) - (parse-int width) - color?)] - (if html? - (wrap-pre-tag output) - output))) + (parse-boolean color))] + (image->ascii (:image ctx) (parse-int width) color? html?))) :handle-malformed (fn [ctx] (:error ctx)) diff --git a/src/java/toascii/images/ImageToAscii.java b/src/java/toascii/images/ImageToAscii.java deleted file mode 100644 index 73cee62..0000000 --- a/src/java/toascii/images/ImageToAscii.java +++ /dev/null @@ -1,83 +0,0 @@ -package toascii.images; - -import java.awt.image.BufferedImage; - -public class ImageToAscii { - static final char[] asciiChars = {'#', 'A', '@', '%', '$', '+', '=', '*', ':', ',', '.', ' '}; - static final int numAsciiChars = asciiChars.length - 1; - static final int spanLength = "X".length(); - static final int lineTerminatorLength = "
".length(); - - // copied from java.lang.Integer.digits (which is private so we can't just reference it, boourns) - static final char[] digits = { - '0' , '1' , '2' , '3' , '4' , '5' , - '6' , '7' , '8' , '9' , 'a' , 'b' , - 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , - 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , - 'o' , 'p' , 'q' , 'r' , 's' , 't' , - 'u' , 'v' , 'w' , 'x' , 'y' , 'z' - }; - - // modification of java.lang.Integer.toUnsignedString -- no garbage generated, but limited to max value - // of 255 ...hence the 'unsigned byte' thing :) - private static void unsignedByteToHex(int unsignedByte, StringBuilder sb) { - for (int i = 0; i < 2; ++i) { - int index = sb.length + 1 - i; - if (unsignedByte != 0) { - sb.chars[index] = digits[unsignedByte & 15]; - unsignedByte >>>= 4; - } else - sb.chars[index] = '0'; - } - sb.length += 2; - } - - public static String convert(BufferedImage image, boolean useColor) { - final int width = image.getWidth(); - final int height = image.getHeight(); - - final int maxLength = (useColor ? - (width * height * spanLength) + (height * lineTerminatorLength) : - (width * height) + height); - - final StringBuilder sb = new StringBuilder(maxLength); - - final int[] pixels = image.getRGB(0, 0, width, height, null, 0, width); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - final int argb = pixels[(y * width) + x]; - final int r = (0x00ff0000 & argb) >> 16; - final int g = (0x0000ff00 & argb) >> 8; - final int b = (0x000000ff & argb); - final double brightness = Math.sqrt((r * r * 0.241f) + - (g * g * 0.691f) + - (b * b * 0.068f)); - int charIndex; - if (brightness == 0.0f) - charIndex = numAsciiChars; - else - charIndex = (int)((brightness / 255.0f) * numAsciiChars); - - final char pixelChar = asciiChars[charIndex > 0 ? charIndex : 0]; - - if (useColor) { - sb.append(""); - sb.append(pixelChar); - sb.append(""); - } else - sb.append(pixelChar); - } - if (useColor) - sb.append("
"); - else - sb.append('\n'); - } - - return sb.toString(); - } -} diff --git a/src/java/toascii/images/StringBuilder.java b/src/java/toascii/images/StringBuilder.java deleted file mode 100644 index 696ddf8..0000000 --- a/src/java/toascii/images/StringBuilder.java +++ /dev/null @@ -1,1204 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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. - */ - -// This StringBuilder implementation is borrowed from libgdx, mainly so that we can fiddle with the -// underlying char array directly. -// Source: https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/utils/StringBuilder.java - -package toascii.images; - -import java.util.Arrays; - -/** A {@link java.lang.StringBuilder} that implements equals and hashcode. - * @see CharSequence - * @see Appendable - * @see java.lang.StringBuilder - * @see String */ -public class StringBuilder implements Appendable, CharSequence { - static final int INITIAL_CAPACITY = 16; - - public char[] chars; - public int length; - - private static final char[] digits = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; - - /** @return the number of characters required to represent the specified value with the specified radix */ - public static int numChars (int value, int radix) { - int result = (value < 0) ? 2 : 1; - while ((value /= radix) != 0) - ++result; - return result; - } - - /** @return the number of characters required to represent the specified value with the specified radix */ - public static int numChars (long value, int radix) { - int result = (value < 0) ? 2 : 1; - while ((value /= radix) != 0) - ++result; - return result; - } - - /* - * Returns the character array. - */ - final char[] getValue () { - return chars; - } - - /** Constructs an instance with an initial capacity of {@code 16}. - * - * @see #capacity() */ - public StringBuilder () { - chars = new char[INITIAL_CAPACITY]; - } - - /** Constructs an instance with the specified capacity. - * - * @param capacity the initial capacity to use. - * @throws NegativeArraySizeException if the specified {@code capacity} is negative. - * @see #capacity() */ - public StringBuilder (int capacity) { - if (capacity < 0) { - throw new NegativeArraySizeException(); - } - chars = new char[capacity]; - } - - /** Constructs an instance that's initialized with the contents of the specified {@code CharSequence}. The capacity of the new - * builder will be the length of the {@code CharSequence} plus 16. - * - * @param seq the {@code CharSequence} to copy into the builder. - * @throws NullPointerException if {@code seq} is {@code null}. */ - public StringBuilder (CharSequence seq) { - this(seq.toString()); - } - - public StringBuilder (StringBuilder builder) { - length = builder.length; - chars = new char[length + INITIAL_CAPACITY]; - System.arraycopy(builder.chars, 0, chars, 0, length); - } - - /** Constructs an instance that's initialized with the contents of the specified {@code String}. The capacity of the new builder - * will be the length of the {@code String} plus 16. - * - * @param string the {@code String} to copy into the builder. - * @throws NullPointerException if {@code str} is {@code null}. */ - public StringBuilder (String string) { - length = string.length(); - chars = new char[length + INITIAL_CAPACITY]; - string.getChars(0, length, chars, 0); - } - - private void enlargeBuffer (int min) { - int newSize = (chars.length >> 1) + chars.length + 2; - char[] newData = new char[min > newSize ? min : newSize]; - System.arraycopy(chars, 0, newData, 0, length); - chars = newData; - } - - final void appendNull () { - int newSize = length + 4; - if (newSize > chars.length) { - enlargeBuffer(newSize); - } - chars[length++] = 'n'; - chars[length++] = 'u'; - chars[length++] = 'l'; - chars[length++] = 'l'; - } - - final void append0 (char[] value) { - int newSize = length + value.length; - if (newSize > chars.length) { - enlargeBuffer(newSize); - } - System.arraycopy(value, 0, chars, length, value.length); - length = newSize; - } - - final void append0 (char[] value, int offset, int length) { - // Force null check of chars first! - if (offset > value.length || offset < 0) { - throw new ArrayIndexOutOfBoundsException("Offset out of bounds: " + offset); - } - if (length < 0 || value.length - offset < length) { - throw new ArrayIndexOutOfBoundsException("Length out of bounds: " + length); - } - - int newSize = this.length + length; - if (newSize > chars.length) { - enlargeBuffer(newSize); - } - System.arraycopy(value, offset, chars, this.length, length); - this.length = newSize; - } - - final void append0 (char ch) { - if (length == chars.length) { - enlargeBuffer(length + 1); - } - chars[length++] = ch; - } - - final void append0 (String string) { - if (string == null) { - appendNull(); - return; - } - int adding = string.length(); - int newSize = length + adding; - if (newSize > chars.length) { - enlargeBuffer(newSize); - } - string.getChars(0, adding, chars, length); - length = newSize; - } - - final void append0 (CharSequence s, int start, int end) { - if (s == null) { - s = "null"; - } - if (start < 0 || end < 0 || start > end || end > s.length()) { - throw new IndexOutOfBoundsException(); - } - - append0(s.subSequence(start, end).toString()); - } - - /** Returns the number of characters that can be held without growing. - * - * @return the capacity - * @see #ensureCapacity - * @see #length */ - public int capacity () { - return chars.length; - } - - /** Retrieves the character at the {@code index}. - * - * @param index the index of the character to retrieve. - * @return the char value. - * @throws IndexOutOfBoundsException if {@code index} is negative or greater than or equal to the current {@link #length()}. */ - public char charAt (int index) { - if (index < 0 || index >= length) { - throw new StringIndexOutOfBoundsException(index); - } - return chars[index]; - } - - final void delete0 (int start, int end) { - if (start >= 0) { - if (end > length) { - end = length; - } - if (end == start) { - return; - } - if (end > start) { - int count = length - end; - if (count >= 0) System.arraycopy(chars, end, chars, start, count); - length -= end - start; - return; - } - } - throw new StringIndexOutOfBoundsException(); - } - - final void deleteCharAt0 (int location) { - if (0 > location || location >= length) { - throw new StringIndexOutOfBoundsException(location); - } - int count = length - location - 1; - if (count > 0) { - System.arraycopy(chars, location + 1, chars, location, count); - } - length--; - } - - /** Ensures that this object has a minimum capacity available before requiring the internal buffer to be enlarged. The general - * policy of this method is that if the {@code minimumCapacity} is larger than the current {@link #capacity()}, then the - * capacity will be increased to the largest value of either the {@code minimumCapacity} or the current capacity multiplied by - * two plus two. Although this is the general policy, there is no guarantee that the capacity will change. - * - * @param min the new minimum capacity to set. */ - public void ensureCapacity (int min) { - if (min > chars.length) { - int twice = (chars.length << 1) + 2; - enlargeBuffer(twice > min ? twice : min); - } - } - - /** Copies the requested sequence of characters to the {@code char[]} passed starting at {@code destStart}. - * - * @param start the inclusive start index of the characters to copy. - * @param end the exclusive end index of the characters to copy. - * @param dest the {@code char[]} to copy the characters to. - * @param destStart the inclusive start index of {@code dest} to begin copying to. - * @throws IndexOutOfBoundsException if the {@code start} is negative, the {@code destStart} is negative, the {@code start} is - * greater than {@code end}, the {@code end} is greater than the current {@link #length()} or - * {@code destStart + end - begin} is greater than {@code dest.length}. */ - public void getChars (int start, int end, char[] dest, int destStart) { - if (start > length || end > length || start > end) { - throw new StringIndexOutOfBoundsException(); - } - System.arraycopy(chars, start, dest, destStart, end - start); - } - - final void insert0 (int index, char[] value) { - if (0 > index || index > length) { - throw new StringIndexOutOfBoundsException(index); - } - if (value.length != 0) { - move(value.length, index); - System.arraycopy(value, 0, value, index, value.length); - length += value.length; - } - } - - final void insert0 (int index, char[] value, int start, int length) { - if (0 <= index && index <= length) { - // start + length could overflow, start/length maybe MaxInt - if (start >= 0 && 0 <= length && length <= value.length - start) { - if (length != 0) { - move(length, index); - System.arraycopy(value, start, chars, index, length); - this.length += length; - } - return; - } - throw new StringIndexOutOfBoundsException("offset " + start + ", length " + length + ", char[].length " + value.length); - } - throw new StringIndexOutOfBoundsException(index); - } - - final void insert0 (int index, char ch) { - if (0 > index || index > length) { - // RI compatible exception type - throw new ArrayIndexOutOfBoundsException(index); - } - move(1, index); - chars[index] = ch; - length++; - } - - final void insert0 (int index, String string) { - if (0 <= index && index <= length) { - if (string == null) { - string = "null"; - } - int min = string.length(); - if (min != 0) { - move(min, index); - string.getChars(0, min, chars, index); - length += min; - } - } else { - throw new StringIndexOutOfBoundsException(index); - } - } - - final void insert0 (int index, CharSequence s, int start, int end) { - if (s == null) { - s = "null"; - } - if (index < 0 || index > length || start < 0 || end < 0 || start > end || end > s.length()) { - throw new IndexOutOfBoundsException(); - } - insert0(index, s.subSequence(start, end).toString()); - } - - /** The current length. - * - * @return the number of characters contained in this instance. */ - public int length () { - return length; - } - - private void move (int size, int index) { - if (chars.length - length >= size) { - System.arraycopy(chars, index, chars, index + size, length - index); // index == count case is no-op - return; - } - int a = length + size, b = (chars.length << 1) + 2; - int newSize = a > b ? a : b; - char[] newData = new char[newSize]; - System.arraycopy(chars, 0, newData, 0, index); - // index == count case is no-op - System.arraycopy(chars, index, newData, index + size, length - index); - chars = newData; - } - - final void replace0 (int start, int end, String string) { - if (start >= 0) { - if (end > length) { - end = length; - } - if (end > start) { - int stringLength = string.length(); - int diff = end - start - stringLength; - if (diff > 0) { // replacing with fewer characters - // index == count case is no-op - System.arraycopy(chars, end, chars, start + stringLength, length - end); - } else if (diff < 0) { - // replacing with more characters...need some room - move(-diff, end); - } - string.getChars(0, stringLength, chars, start); - length -= diff; - return; - } - if (start == end) { - if (string == null) { - throw new NullPointerException(); - } - insert0(start, string); - return; - } - } - throw new StringIndexOutOfBoundsException(); - } - - final void reverse0 () { - if (length < 2) { - return; - } - int end = length - 1; - char frontHigh = chars[0]; - char endLow = chars[end]; - boolean allowFrontSur = true, allowEndSur = true; - for (int i = 0, mid = length / 2; i < mid; i++, --end) { - char frontLow = chars[i + 1]; - char endHigh = chars[end - 1]; - boolean surAtFront = allowFrontSur && frontLow >= 0xdc00 && frontLow <= 0xdfff && frontHigh >= 0xd800 - && frontHigh <= 0xdbff; - if (surAtFront && length < 3) { - return; - } - boolean surAtEnd = allowEndSur && endHigh >= 0xd800 && endHigh <= 0xdbff && endLow >= 0xdc00 && endLow <= 0xdfff; - allowFrontSur = allowEndSur = true; - if (surAtFront == surAtEnd) { - if (surAtFront) { - // both surrogates - chars[end] = frontLow; - chars[end - 1] = frontHigh; - chars[i] = endHigh; - chars[i + 1] = endLow; - frontHigh = chars[i + 2]; - endLow = chars[end - 2]; - i++; - end--; - } else { - // neither surrogates - chars[end] = frontHigh; - chars[i] = endLow; - frontHigh = frontLow; - endLow = endHigh; - } - } else { - if (surAtFront) { - // surrogate only at the front - chars[end] = frontLow; - chars[i] = endLow; - endLow = endHigh; - allowFrontSur = false; - } else { - // surrogate only at the end - chars[end] = frontHigh; - chars[i] = endHigh; - frontHigh = frontLow; - allowEndSur = false; - } - } - } - if ((length & 1) == 1 && (!allowFrontSur || !allowEndSur)) { - chars[end] = allowFrontSur ? endLow : frontHigh; - } - } - - /** Sets the character at the {@code index}. - * - * @param index the zero-based index of the character to replace. - * @param ch the character to set. - * @throws IndexOutOfBoundsException if {@code index} is negative or greater than or equal to the current {@link #length()}. */ - public void setCharAt (int index, char ch) { - if (0 > index || index >= length) { - throw new StringIndexOutOfBoundsException(index); - } - chars[index] = ch; - } - - /** Sets the current length to a new value. If the new length is larger than the current length, then the new characters at the - * end of this object will contain the {@code char} value of {@code \u0000}. - * - * @param newLength the new length of this StringBuilder. - * @exception IndexOutOfBoundsException if {@code length < 0}. - * @see #length */ - public void setLength (int newLength) { - if (newLength < 0) { - throw new StringIndexOutOfBoundsException(newLength); - } - if (newLength > chars.length) { - enlargeBuffer(newLength); - } else { - if (length < newLength) { - Arrays.fill(chars, length, newLength, (char)0); - } - } - length = newLength; - } - - /** Returns the String value of the subsequence from the {@code start} index to the current end. - * - * @param start the inclusive start index to begin the subsequence. - * @return a String containing the subsequence. - * @throws StringIndexOutOfBoundsException if {@code start} is negative or greater than the current {@link #length()}. */ - public String substring (int start) { - if (0 <= start && start <= length) { - if (start == length) { - return ""; - } - - // Remove String sharing for more performance - return new String(chars, start, length - start); - } - throw new StringIndexOutOfBoundsException(start); - } - - /** Returns the String value of the subsequence from the {@code start} index to the {@code end} index. - * - * @param start the inclusive start index to begin the subsequence. - * @param end the exclusive end index to end the subsequence. - * @return a String containing the subsequence. - * @throws StringIndexOutOfBoundsException if {@code start} is negative, greater than {@code end} or if {@code end} is greater - * than the current {@link #length()}. */ - public String substring (int start, int end) { - if (0 <= start && start <= end && end <= length) { - if (start == end) { - return ""; - } - - // Remove String sharing for more performance - return new String(chars, start, end - start); - } - throw new StringIndexOutOfBoundsException(); - } - - /** Returns the current String representation. - * - * @return a String containing the characters in this instance. */ - @Override - public String toString () { - if (length == 0) return ""; - return new String(chars, 0, length); - } - - /** Returns a {@code CharSequence} of the subsequence from the {@code start} index to the {@code end} index. - * - * @param start the inclusive start index to begin the subsequence. - * @param end the exclusive end index to end the subsequence. - * @return a CharSequence containing the subsequence. - * @throws IndexOutOfBoundsException if {@code start} is negative, greater than {@code end} or if {@code end} is greater than - * the current {@link #length()}. - * @since 1.4 */ - public CharSequence subSequence (int start, int end) { - return substring(start, end); - } - - /** Searches for the first index of the specified character. The search for the character starts at the beginning and moves - * towards the end. - * - * @param string the string to find. - * @return the index of the specified character, -1 if the character isn't found. - * @see #lastIndexOf(String) - * @since 1.4 */ - public int indexOf (String string) { - return indexOf(string, 0); - } - - /** Searches for the index of the specified character. The search for the character starts at the specified offset and moves - * towards the end. - * - * @param subString the string to find. - * @param start the starting offset. - * @return the index of the specified character, -1 if the character isn't found - * @see #lastIndexOf(String,int) - * @since 1.4 */ - public int indexOf (String subString, int start) { - if (start < 0) { - start = 0; - } - int subCount = subString.length(); - if (subCount > 0) { - if (subCount + start > length) { - return -1; - } - char firstChar = subString.charAt(0); - while (true) { - int i = start; - boolean found = false; - for (; i < length; i++) { - if (chars[i] == firstChar) { - found = true; - break; - } - } - if (!found || subCount + i > length) { - return -1; // handles subCount > count || start >= count - } - int o1 = i, o2 = 0; - while (++o2 < subCount && chars[++o1] == subString.charAt(o2)) { - // Intentionally empty - } - if (o2 == subCount) { - return i; - } - start = i + 1; - } - } - return start < length || start == 0 ? start : length; - } - - /** Searches for the last index of the specified character. The search for the character starts at the end and moves towards the - * beginning. - * - * @param string the string to find. - * @return the index of the specified character, -1 if the character isn't found. - * @throws NullPointerException if {@code string} is {@code null}. - * @see String#lastIndexOf(java.lang.String) - * @since 1.4 */ - public int lastIndexOf (String string) { - return lastIndexOf(string, length); - } - - /** Searches for the index of the specified character. The search for the character starts at the specified offset and moves - * towards the beginning. - * - * @param subString the string to find. - * @param start the starting offset. - * @return the index of the specified character, -1 if the character isn't found. - * @throws NullPointerException if {@code subString} is {@code null}. - * @see String#lastIndexOf(String,int) - * @since 1.4 */ - public int lastIndexOf (String subString, int start) { - int subCount = subString.length(); - if (subCount <= length && start >= 0) { - if (subCount > 0) { - if (start > length - subCount) { - start = length - subCount; // count and subCount are both - } - // >= 1 - char firstChar = subString.charAt(0); - while (true) { - int i = start; - boolean found = false; - for (; i >= 0; --i) { - if (chars[i] == firstChar) { - found = true; - break; - } - } - if (!found) { - return -1; - } - int o1 = i, o2 = 0; - while (++o2 < subCount && chars[++o1] == subString.charAt(o2)) { - // Intentionally empty - } - if (o2 == subCount) { - return i; - } - start = i - 1; - } - } - return start < length ? start : length; - } - return -1; - } - - /** Trims off any extra capacity beyond the current length. Note, this method is NOT guaranteed to change the capacity of this - * object. - * - * @since 1.5 */ - public void trimToSize () { - if (length < chars.length) { - char[] newValue = new char[length]; - System.arraycopy(chars, 0, newValue, 0, length); - chars = newValue; - } - } - - /** Retrieves the Unicode code point value at the {@code index}. - * - * @param index the index to the {@code char} code unit. - * @return the Unicode code point value. - * @throws IndexOutOfBoundsException if {@code index} is negative or greater than or equal to {@link #length()}. - * @see Character - * @see Character#codePointAt(char[], int, int) - * @since 1.5 */ - public int codePointAt (int index) { - if (index < 0 || index >= length) { - throw new StringIndexOutOfBoundsException(index); - } - return Character.codePointAt(chars, index, length); - } - - /** Retrieves the Unicode code point value that precedes the {@code index}. - * - * @param index the index to the {@code char} code unit within this object. - * @return the Unicode code point value. - * @throws IndexOutOfBoundsException if {@code index} is less than 1 or greater than {@link #length()}. - * @see Character - * @see Character#codePointBefore(char[], int, int) - * @since 1.5 */ - public int codePointBefore (int index) { - if (index < 1 || index > length) { - throw new StringIndexOutOfBoundsException(index); - } - return Character.codePointBefore(chars, index); - } - - /** Calculates the number of Unicode code points between {@code beginIndex} and {@code endIndex}. - * - * @param beginIndex the inclusive beginning index of the subsequence. - * @param endIndex the exclusive end index of the subsequence. - * @return the number of Unicode code points in the subsequence. - * @throws IndexOutOfBoundsException if {@code beginIndex} is negative or greater than {@code endIndex} or {@code endIndex} is - * greater than {@link #length()}. - * @see Character - * @see Character#codePointCount(char[], int, int) - * @since 1.5 */ - public int codePointCount (int beginIndex, int endIndex) { - if (beginIndex < 0 || endIndex > length || beginIndex > endIndex) { - throw new StringIndexOutOfBoundsException(); - } - return Character.codePointCount(chars, beginIndex, endIndex - beginIndex); - } - - /** Returns the index that is offset {@code codePointOffset} code points from {@code index}. - * - * @param index the index to calculate the offset from. - * @param codePointOffset the number of code points to count. - * @return the index that is {@code codePointOffset} code points away from index. - * @throws IndexOutOfBoundsException if {@code index} is negative or greater than {@link #length()} or if there aren't enough - * code points before or after {@code index} to match {@code codePointOffset}. - * @see Character - * @see Character#offsetByCodePoints(char[], int, int, int, int) - * @since 1.5 */ - public int offsetByCodePoints (int index, int codePointOffset) { - return Character.offsetByCodePoints(chars, 0, length, index, codePointOffset); - } - - /** Appends the string representation of the specified {@code boolean} value. The {@code boolean} value is converted to a String - * according to the rule defined by {@link String#valueOf(boolean)}. - * - * @param b the {@code boolean} value to append. - * @return this builder. - * @see String#valueOf(boolean) */ - public StringBuilder append (boolean b) { - append0(b ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$ - return this; - } - - /** Appends the string representation of the specified {@code char} value. The {@code char} value is converted to a string - * according to the rule defined by {@link String#valueOf(char)}. - * - * @param c the {@code char} value to append. - * @return this builder. - * @see String#valueOf(char) */ - public StringBuilder append (char c) { - append0(c); - return this; - } - - /** Appends the string representation of the specified {@code int} value. The {@code int} value is converted to a string without - * memory allocation. - * - * @param value the {@code int} value to append. - * @return this builder. - * @see String#valueOf(int) */ - public StringBuilder append (int value) { - return append(value, 0); - } - - /** Appends the string representation of the specified {@code int} value. The {@code int} value is converted to a string without - * memory allocation. - * - * @param value the {@code int} value to append. - * @param minLength the minimum number of characters to add - * @return this builder. - * @see String#valueOf(int) */ - public StringBuilder append (int value, int minLength) { - return append(value, minLength, '0'); - } - - /** Appends the string representation of the specified {@code int} value. The {@code int} value is converted to a string without - * memory allocation. - * - * @param value the {@code int} value to append. - * @param minLength the minimum number of characters to add - * @param prefix the character to use as prefix - * @return this builder. - * @see String#valueOf(int) */ - public StringBuilder append (int value, final int minLength, final char prefix) { - if (value == Integer.MIN_VALUE) { - append0("-2147483648"); - return this; - } - if (value < 0) { - append0('-'); - value = -value; - } - if (minLength > 1) { - for (int j = minLength - numChars(value, 10); j > 0; --j) - append(prefix); - } - if (value >= 10000) { - if (value >= 1000000000) append0(digits[(int)((long)value % 10000000000L / 1000000000L)]); - if (value >= 100000000) append0(digits[value % 1000000000 / 100000000]); - if (value >= 10000000) append0(digits[value % 100000000 / 10000000]); - if (value >= 1000000) append0(digits[value % 10000000 / 1000000]); - if (value >= 100000) append0(digits[value % 1000000 / 100000]); - append0(digits[value % 100000 / 10000]); - } - if (value >= 1000) append0(digits[value % 10000 / 1000]); - if (value >= 100) append0(digits[value % 1000 / 100]); - if (value >= 10) append0(digits[value % 100 / 10]); - append0(digits[value % 10]); - return this; - } - - /** Appends the string representation of the specified {@code long} value. The {@code long} value is converted to a string - * without memory allocation. - * - * @param value the {@code long} value. - * @return this builder. */ - public StringBuilder append (long value) { - return append(value, 0); - } - - /** Appends the string representation of the specified {@code long} value. The {@code long} value is converted to a string - * without memory allocation. - * - * @param value the {@code long} value. - * @param minLength the minimum number of characters to add - * @return this builder. */ - public StringBuilder append (long value, int minLength) { - return append(value, minLength, '0'); - } - - /** Appends the string representation of the specified {@code long} value. The {@code long} value is converted to a string - * without memory allocation. - * - * @param value the {@code long} value. - * @param minLength the minimum number of characters to add - * @param prefix the character to use as prefix - * @return this builder. */ - public StringBuilder append (long value, int minLength, char prefix) { - if (value == Long.MIN_VALUE) { - append0("-9223372036854775808"); - return this; - } - if (value < 0L) { - append0('-'); - value = -value; - } - if (minLength > 1) { - for (int j = minLength - numChars(value, 10); j > 0; --j) - append(prefix); - } - if (value >= 10000) { - if (value >= 1000000000000000000L) append0(digits[(int)(value % 10000000000000000000D / 1000000000000000000L)]); - if (value >= 100000000000000000L) append0(digits[(int)(value % 1000000000000000000L / 100000000000000000L)]); - if (value >= 10000000000000000L) append0(digits[(int)(value % 100000000000000000L / 10000000000000000L)]); - if (value >= 1000000000000000L) append0(digits[(int)(value % 10000000000000000L / 1000000000000000L)]); - if (value >= 100000000000000L) append0(digits[(int)(value % 1000000000000000L / 100000000000000L)]); - if (value >= 10000000000000L) append0(digits[(int)(value % 100000000000000L / 10000000000000L)]); - if (value >= 1000000000000L) append0(digits[(int)(value % 10000000000000L / 1000000000000L)]); - if (value >= 100000000000L) append0(digits[(int)(value % 1000000000000L / 100000000000L)]); - if (value >= 10000000000L) append0(digits[(int)(value % 100000000000L / 10000000000L)]); - if (value >= 1000000000L) append0(digits[(int)(value % 10000000000L / 1000000000L)]); - if (value >= 100000000L) append0(digits[(int)(value % 1000000000L / 100000000L)]); - if (value >= 10000000L) append0(digits[(int)(value % 100000000L / 10000000L)]); - if (value >= 1000000L) append0(digits[(int)(value % 10000000L / 1000000L)]); - if (value >= 100000L) append0(digits[(int)(value % 1000000L / 100000L)]); - append0(digits[(int)(value % 100000L / 10000L)]); - } - if (value >= 1000L) append0(digits[(int)(value % 10000L / 1000L)]); - if (value >= 100L) append0(digits[(int)(value % 1000L / 100L)]); - if (value >= 10L) append0(digits[(int)(value % 100L / 10L)]); - append0(digits[(int)(value % 10L)]); - return this; - } - - /** Appends the string representation of the specified {@code float} value. The {@code float} value is converted to a string - * according to the rule defined by {@link String#valueOf(float)}. - * - * @param f the {@code float} value to append. - * @return this builder. */ - public StringBuilder append (float f) { - append0(Float.toString(f)); - return this; - } - - /** Appends the string representation of the specified {@code double} value. The {@code double} value is converted to a string - * according to the rule defined by {@link String#valueOf(double)}. - * - * @param d the {@code double} value to append. - * @return this builder. - * @see String#valueOf(double) */ - public StringBuilder append (double d) { - append0(Double.toString(d)); - return this; - } - - /** Appends the string representation of the specified {@code Object}. The {@code Object} value is converted to a string - * according to the rule defined by {@link String#valueOf(Object)}. - * - * @param obj the {@code Object} to append. - * @return this builder. - * @see String#valueOf(Object) */ - public StringBuilder append (Object obj) { - if (obj == null) { - appendNull(); - } else { - append0(obj.toString()); - } - return this; - } - - /** Appends the contents of the specified string. If the string is {@code null}, then the string {@code "null"} is appended. - * - * @param str the string to append. - * @return this builder. */ - public StringBuilder append (String str) { - append0(str); - return this; - } - - /** Appends the string representation of the specified {@code char[]}. The {@code char[]} is converted to a string according to - * the rule defined by {@link String#valueOf(char[])}. - * - * @param ch the {@code char[]} to append.. - * @return this builder. - * @see String#valueOf(char[]) */ - public StringBuilder append (char[] ch) { - append0(ch); - return this; - } - - /** Appends the string representation of the specified subset of the {@code char[]}. The {@code char[]} value is converted to a - * String according to the rule defined by {@link String#valueOf(char[],int,int)}. - * - * @param str the {@code char[]} to append. - * @param offset the inclusive offset index. - * @param len the number of characters. - * @return this builder. - * @throws ArrayIndexOutOfBoundsException if {@code offset} and {@code len} do not specify a valid subsequence. - * @see String#valueOf(char[],int,int) */ - public StringBuilder append (char[] str, int offset, int len) { - append0(str, offset, len); - return this; - } - - /** Appends the string representation of the specified {@code CharSequence}. If the {@code CharSequence} is {@code null}, then - * the string {@code "null"} is appended. - * - * @param csq the {@code CharSequence} to append. - * @return this builder. */ - public StringBuilder append (CharSequence csq) { - if (csq == null) { - appendNull(); - } else { - append0(csq.toString()); - } - return this; - } - - public StringBuilder append (StringBuilder builder) { - if (builder == null) - appendNull(); - else - append0(builder.chars, 0, builder.length); - return this; - } - - /** Appends the string representation of the specified subsequence of the {@code CharSequence}. If the {@code CharSequence} is - * {@code null}, then the string {@code "null"} is used to extract the subsequence from. - * - * @param csq the {@code CharSequence} to append. - * @param start the beginning index. - * @param end the ending index. - * @return this builder. - * @throws IndexOutOfBoundsException if {@code start} or {@code end} are negative, {@code start} is greater than {@code end} or - * {@code end} is greater than the length of {@code csq}. */ - public StringBuilder append (CharSequence csq, int start, int end) { - append0(csq, start, end); - return this; - } - - public StringBuilder append (StringBuilder builder, int start, int end) { - if (builder == null) - appendNull(); - else - append0(builder.chars, start, end); - return this; - } - - /** Appends the encoded Unicode code point. The code point is converted to a {@code char[]} as defined by - * {@link Character#toChars(int)}. - * - * @param codePoint the Unicode code point to encode and append. - * @return this builder. - * @see Character#toChars(int) */ - public StringBuilder appendCodePoint (int codePoint) { - append0(Character.toChars(codePoint)); - return this; - } - - /** Deletes a sequence of characters specified by {@code start} and {@code end}. Shifts any remaining characters to the left. - * - * @param start the inclusive start index. - * @param end the exclusive end index. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code start} is less than zero, greater than the current length or greater than - * {@code end}. */ - public StringBuilder delete (int start, int end) { - delete0(start, end); - return this; - } - - /** Deletes the character at the specified index. shifts any remaining characters to the left. - * - * @param index the index of the character to delete. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code index} is less than zero or is greater than or equal to the current - * length. */ - public StringBuilder deleteCharAt (int index) { - deleteCharAt0(index); - return this; - } - - /** Inserts the string representation of the specified {@code boolean} value at the specified {@code offset}. The - * {@code boolean} value is converted to a string according to the rule defined by {@link String#valueOf(boolean)}. - * - * @param offset the index to insert at. - * @param b the {@code boolean} value to insert. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length}. - * @see String#valueOf(boolean) */ - public StringBuilder insert (int offset, boolean b) { - insert0(offset, b ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$ - return this; - } - - /** Inserts the string representation of the specified {@code char} value at the specified {@code offset}. The {@code char} - * value is converted to a string according to the rule defined by {@link String#valueOf(char)}. - * - * @param offset the index to insert at. - * @param c the {@code char} value to insert. - * @return this builder. - * @throws IndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length()}. - * @see String#valueOf(char) */ - public StringBuilder insert (int offset, char c) { - insert0(offset, c); - return this; - } - - /** Inserts the string representation of the specified {@code int} value at the specified {@code offset}. The {@code int} value - * is converted to a String according to the rule defined by {@link String#valueOf(int)}. - * - * @param offset the index to insert at. - * @param i the {@code int} value to insert. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length()}. - * @see String#valueOf(int) */ - public StringBuilder insert (int offset, int i) { - insert0(offset, Integer.toString(i)); - return this; - } - - /** Inserts the string representation of the specified {@code long} value at the specified {@code offset}. The {@code long} - * value is converted to a String according to the rule defined by {@link String#valueOf(long)}. - * - * @param offset the index to insert at. - * @param l the {@code long} value to insert. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code offset} is negative or greater than the current {code length()}. - * @see String#valueOf(long) */ - public StringBuilder insert (int offset, long l) { - insert0(offset, Long.toString(l)); - return this; - } - - /** Inserts the string representation of the specified {@code float} value at the specified {@code offset}. The {@code float} - * value is converted to a string according to the rule defined by {@link String#valueOf(float)}. - * - * @param offset the index to insert at. - * @param f the {@code float} value to insert. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length()}. - * @see String#valueOf(float) */ - public StringBuilder insert (int offset, float f) { - insert0(offset, Float.toString(f)); - return this; - } - - /** Inserts the string representation of the specified {@code double} value at the specified {@code offset}. The {@code double} - * value is converted to a String according to the rule defined by {@link String#valueOf(double)}. - * - * @param offset the index to insert at. - * @param d the {@code double} value to insert. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length()}. - * @see String#valueOf(double) */ - public StringBuilder insert (int offset, double d) { - insert0(offset, Double.toString(d)); - return this; - } - - /** Inserts the string representation of the specified {@code Object} at the specified {@code offset}. The {@code Object} value - * is converted to a String according to the rule defined by {@link String#valueOf(Object)}. - * - * @param offset the index to insert at. - * @param obj the {@code Object} to insert. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length()}. - * @see String#valueOf(Object) */ - public StringBuilder insert (int offset, Object obj) { - insert0(offset, obj == null ? "null" : obj.toString()); //$NON-NLS-1$ - return this; - } - - /** Inserts the specified string at the specified {@code offset}. If the specified string is null, then the String - * {@code "null"} is inserted. - * - * @param offset the index to insert at. - * @param str the {@code String} to insert. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length()}. */ - public StringBuilder insert (int offset, String str) { - insert0(offset, str); - return this; - } - - /** Inserts the string representation of the specified {@code char[]} at the specified {@code offset}. The {@code char[]} value - * is converted to a String according to the rule defined by {@link String#valueOf(char[])}. - * - * @param offset the index to insert at. - * @param ch the {@code char[]} to insert. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length()}. - * @see String#valueOf(char[]) */ - public StringBuilder insert (int offset, char[] ch) { - insert0(offset, ch); - return this; - } - - /** Inserts the string representation of the specified subsequence of the {@code char[]} at the specified {@code offset}. The - * {@code char[]} value is converted to a String according to the rule defined by {@link String#valueOf(char[],int,int)}. - * - * @param offset the index to insert at. - * @param str the {@code char[]} to insert. - * @param strOffset the inclusive index. - * @param strLen the number of characters. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length()}, or - * {@code strOffset} and {@code strLen} do not specify a valid subsequence. - * @see String#valueOf(char[],int,int) */ - public StringBuilder insert (int offset, char[] str, int strOffset, int strLen) { - insert0(offset, str, strOffset, strLen); - return this; - } - - /** Inserts the string representation of the specified {@code CharSequence} at the specified {@code offset}. The - * {@code CharSequence} is converted to a String as defined by {@link CharSequence#toString()}. If {@code s} is {@code null}, - * then the String {@code "null"} is inserted. - * - * @param offset the index to insert at. - * @param s the {@code CharSequence} to insert. - * @return this builder. - * @throws IndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length()}. - * @see CharSequence#toString() */ - public StringBuilder insert (int offset, CharSequence s) { - insert0(offset, s == null ? "null" : s.toString()); //$NON-NLS-1$ - return this; - } - - /** Inserts the string representation of the specified subsequence of the {@code CharSequence} at the specified {@code offset}. - * The {@code CharSequence} is converted to a String as defined by {@link CharSequence#subSequence(int, int)}. If the - * {@code CharSequence} is {@code null}, then the string {@code "null"} is used to determine the subsequence. - * - * @param offset the index to insert at. - * @param s the {@code CharSequence} to insert. - * @param start the start of the subsequence of the character sequence. - * @param end the end of the subsequence of the character sequence. - * @return this builder. - * @throws IndexOutOfBoundsException if {@code offset} is negative or greater than the current {@code length()}, or - * {@code start} and {@code end} do not specify a valid subsequence. - * @see CharSequence#subSequence(int, int) */ - public StringBuilder insert (int offset, CharSequence s, int start, int end) { - insert0(offset, s, start, end); - return this; - } - - /** Replaces the specified subsequence in this builder with the specified string. - * - * @param start the inclusive begin index. - * @param end the exclusive end index. - * @param str the replacement string. - * @return this builder. - * @throws StringIndexOutOfBoundsException if {@code start} is negative, greater than the current {@code length()} or greater - * than {@code end}. - * @throws NullPointerException if {@code str} is {@code null}. */ - public StringBuilder replace (int start, int end, String str) { - replace0(start, end, str); - return this; - } - - /** Reverses the order of characters in this builder. - * - * @return this buffer. */ - public StringBuilder reverse () { - reverse0(); - return this; - } - - public int hashCode () { - final int prime = 31; - int result = 1; - result = prime + length; - result = prime * result + Arrays.hashCode(chars); - return result; - } - - public boolean equals (Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - StringBuilder other = (StringBuilder)obj; - int length = this.length; - if (length != other.length) return false; - char[] chars = this.chars; - char[] chars2 = other.chars; - if (chars == chars2) return true; - if (chars == null || chars2 == null) return false; - for (int i = 0; i < length; i++) - if (chars[i] != chars2[i]) return false; - return true; - } -} \ No newline at end of file diff --git a/test/images/test.png b/test/images/test.png deleted file mode 100644 index 102174a..0000000 Binary files a/test/images/test.png and /dev/null differ