1275 lines
26 KiB
Haxe
1275 lines
26 KiB
Haxe
package lime.graphics;
|
|
|
|
|
|
import haxe.crypto.BaseCode;
|
|
import haxe.io.Bytes;
|
|
import haxe.io.BytesInput;
|
|
import haxe.io.BytesOutput;
|
|
import lime.app.Application;
|
|
import lime.graphics.format.BMP;
|
|
import lime.graphics.format.JPEG;
|
|
import lime.graphics.format.PNG;
|
|
import lime.graphics.utils.ImageCanvasUtil;
|
|
import lime.graphics.utils.ImageDataUtil;
|
|
import lime.math.ColorMatrix;
|
|
import lime.math.Rectangle;
|
|
import lime.math.Vector2;
|
|
import lime.utils.ByteArray;
|
|
import lime.utils.UInt8Array;
|
|
import lime.system.System;
|
|
|
|
#if (js && html5)
|
|
import js.html.CanvasElement;
|
|
import js.html.ImageElement;
|
|
import js.html.Image in JSImage;
|
|
import js.Browser;
|
|
#elseif flash
|
|
import flash.display.BitmapData;
|
|
import flash.geom.Matrix;
|
|
#end
|
|
|
|
#if format
|
|
import format.png.Data;
|
|
import format.png.Reader;
|
|
import format.png.Tools;
|
|
import format.png.Writer;
|
|
import format.tools.Deflate;
|
|
#if sys
|
|
import sys.io.File;
|
|
#end
|
|
#end
|
|
|
|
@:allow(lime.graphics.util.ImageCanvasUtil)
|
|
@:allow(lime.graphics.util.ImageDataUtil)
|
|
@:access(lime.app.Application)
|
|
@:access(lime.math.ColorMatrix)
|
|
@:access(lime.math.Rectangle)
|
|
@:access(lime.math.Vector2)
|
|
|
|
@:autoBuild(lime.Assets.embedImage())
|
|
|
|
|
|
class Image {
|
|
|
|
|
|
private static var __base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
private static var __base64Encoder:BaseCode;
|
|
|
|
public var buffer:ImageBuffer;
|
|
public var data (get, set):UInt8Array;
|
|
public var dirty:Bool;
|
|
public var height:Int;
|
|
public var offsetX:Int;
|
|
public var offsetY:Int;
|
|
public var powerOfTwo (get, set):Bool;
|
|
public var premultiplied (get, set):Bool;
|
|
public var rect (get, null):Rectangle;
|
|
public var src (get, set):Dynamic;
|
|
public var transparent (get, set):Bool;
|
|
public var type:ImageType;
|
|
public var width:Int;
|
|
public var x:Float;
|
|
public var y:Float;
|
|
|
|
|
|
public function new (buffer:ImageBuffer = null, offsetX:Int = 0, offsetY:Int = 0, width:Int = -1, height:Int = -1, color:Null<Int> = null, type:ImageType = null) {
|
|
|
|
this.offsetX = offsetX;
|
|
this.offsetY = offsetY;
|
|
this.width = width;
|
|
this.height = height;
|
|
|
|
if (type == null) {
|
|
|
|
if (Application.current != null && Application.current.renderer != null) {
|
|
|
|
this.type = switch (Application.current.renderer.context) {
|
|
|
|
case DOM (_), CANVAS (_): CANVAS;
|
|
case FLASH (_): FLASH;
|
|
default: DATA;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.type = DATA;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.type = type;
|
|
|
|
}
|
|
|
|
if (buffer == null) {
|
|
|
|
if (width > 0 && height > 0) {
|
|
|
|
switch (this.type) {
|
|
|
|
case CANVAS:
|
|
|
|
this.buffer = new ImageBuffer (null, width, height);
|
|
ImageCanvasUtil.createCanvas (this, width, height);
|
|
|
|
if (color != null) {
|
|
|
|
fillRect (new Rectangle (0, 0, width, height), color);
|
|
|
|
}
|
|
|
|
case DATA:
|
|
|
|
this.buffer = new ImageBuffer (new UInt8Array (width * height * 4), width, height);
|
|
|
|
if (color != null) {
|
|
|
|
fillRect (new Rectangle (0, 0, width, height), color);
|
|
|
|
}
|
|
|
|
case FLASH:
|
|
|
|
#if flash
|
|
this.buffer = new ImageBuffer (null, width, height);
|
|
this.buffer.src = new BitmapData (width, height, true, ((color & 0xFF) << 24) | (color >> 8));
|
|
#end
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
__fromImageBuffer (buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function clone ():Image {
|
|
|
|
var image = new Image (buffer.clone (), offsetX, offsetY, width, height, null, type);
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
public function colorTransform (rect:Rectangle, colorMatrix:ColorMatrix):Void {
|
|
|
|
rect = __clipRect (rect);
|
|
if (buffer == null || rect == null) return;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
ImageCanvasUtil.colorTransform (this, rect, colorMatrix);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
ImageDataUtil.colorTransform (this, rect, colorMatrix);
|
|
|
|
case FLASH:
|
|
|
|
rect.offset (offsetX, offsetY);
|
|
buffer.__srcBitmapData.colorTransform (rect.__toFlashRectangle (), colorMatrix.__toFlashColorTransform ());
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function copyChannel (sourceImage:Image, sourceRect:Rectangle, destPoint:Vector2, sourceChannel:ImageChannel, destChannel:ImageChannel):Void {
|
|
|
|
sourceRect = __clipRect (sourceRect);
|
|
if (buffer == null || sourceRect == null) return;
|
|
|
|
if (destChannel == ALPHA && !transparent) return;
|
|
if (sourceRect.width <= 0 || sourceRect.height <= 0) return;
|
|
if (sourceRect.x + sourceRect.width > sourceImage.width) sourceRect.width = sourceImage.width - sourceRect.x;
|
|
if (sourceRect.y + sourceRect.height > sourceImage.height) sourceRect.height = sourceImage.height - sourceRect.y;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
ImageCanvasUtil.copyChannel (this, sourceImage, sourceRect, destPoint, sourceChannel, destChannel);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
ImageDataUtil.copyChannel (this, sourceImage, sourceRect, destPoint, sourceChannel, destChannel);
|
|
|
|
case FLASH:
|
|
|
|
var srcChannel = switch (sourceChannel) {
|
|
case RED: 1;
|
|
case GREEN: 2;
|
|
case BLUE: 4;
|
|
case ALPHA: 8;
|
|
}
|
|
|
|
var dstChannel = switch (destChannel) {
|
|
case RED: 1;
|
|
case GREEN: 2;
|
|
case BLUE: 4;
|
|
case ALPHA: 8;
|
|
}
|
|
|
|
sourceRect.offset (sourceImage.offsetX, sourceImage.offsetY);
|
|
destPoint.offset (offsetX, offsetY);
|
|
|
|
buffer.__srcBitmapData.copyChannel (sourceImage.buffer.src, sourceRect.__toFlashRectangle (), destPoint.__toFlashPoint (), srcChannel, dstChannel);
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function copyPixels (sourceImage:Image, sourceRect:Rectangle, destPoint:Vector2, alphaImage:Image = null, alphaPoint:Vector2 = null, mergeAlpha:Bool = false):Void {
|
|
|
|
if (buffer == null || sourceImage == null) return;
|
|
|
|
if (sourceRect.x + sourceRect.width > sourceImage.width) sourceRect.width = sourceImage.width - sourceRect.x;
|
|
if (sourceRect.y + sourceRect.height > sourceImage.height) sourceRect.height = sourceImage.height - sourceRect.y;
|
|
if (sourceRect.width <= 0 || sourceRect.height <= 0) return;
|
|
|
|
if (destPoint.x + sourceRect.width > width) sourceRect.width = width - destPoint.x;
|
|
if (destPoint.y + sourceRect.height > height) sourceRect.height = height - destPoint.y;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
ImageCanvasUtil.convertToCanvas (this);
|
|
ImageCanvasUtil.copyPixels (this, sourceImage, sourceRect, destPoint, alphaImage, alphaPoint, mergeAlpha);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
ImageCanvasUtil.convertToData (sourceImage);
|
|
#end
|
|
|
|
ImageDataUtil.copyPixels (this, sourceImage, sourceRect, destPoint, alphaImage, alphaPoint, mergeAlpha);
|
|
|
|
case FLASH:
|
|
|
|
sourceRect.offset (sourceImage.offsetX, sourceImage.offsetY);
|
|
destPoint.offset (offsetX, offsetY);
|
|
|
|
if (alphaImage != null && alphaPoint != null) {
|
|
|
|
alphaPoint.offset (alphaImage.offsetX, alphaImage.offsetY);
|
|
|
|
}
|
|
|
|
buffer.__srcBitmapData.copyPixels (sourceImage.buffer.__srcBitmapData, sourceRect.__toFlashRectangle (), destPoint.__toFlashPoint (), alphaImage != null ? alphaImage.buffer.src : null, alphaPoint != null ? alphaPoint.__toFlashPoint () : null, mergeAlpha);
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function encode (format:String = "png", quality:Int = 90):ByteArray {
|
|
|
|
#if (!html5 && !flash)
|
|
|
|
switch (format) {
|
|
|
|
case "png":
|
|
|
|
return PNG.encode (this);
|
|
|
|
case "jpg", "jpeg":
|
|
|
|
return JPEG.encode (this, quality);
|
|
|
|
case "bmp":
|
|
|
|
return BMP.encode (this);
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
public function fillRect (rect:Rectangle, color:Int, format:PixelFormat = null):Void {
|
|
|
|
rect = __clipRect (rect);
|
|
if (buffer == null || rect == null) return;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
ImageCanvasUtil.fillRect (this, rect, color, format);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
ImageDataUtil.fillRect (this, rect, color, format);
|
|
|
|
case FLASH:
|
|
|
|
rect.offset (offsetX, offsetY);
|
|
if (format == null || format == RGBA) color = ((color & 0xFF) << 24) | (color >> 8);
|
|
buffer.__srcBitmapData.fillRect (rect.__toFlashRectangle (), color);
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function floodFill (x:Int, y:Int, color:Int, format:PixelFormat = null):Void {
|
|
|
|
if (buffer == null) return;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
ImageCanvasUtil.floodFill (this, x, y, color, format);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
ImageDataUtil.floodFill (this, x, y, color, format);
|
|
|
|
case FLASH:
|
|
|
|
if (format == null || format == RGBA) color = ((color & 0xFF) << 24) | (color >> 8);
|
|
buffer.__srcBitmapData.floodFill (x + offsetX, y + offsetY, color);
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public static function fromBase64 (base64:String, type:String, onload:Image -> Void):Image {
|
|
|
|
var image = new Image ();
|
|
image.__fromBase64 (base64, type, onload);
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
public static function fromBitmapData (bitmapData:#if flash BitmapData #else Dynamic #end):Image {
|
|
|
|
var buffer = new ImageBuffer (null, bitmapData.width, bitmapData.height);
|
|
buffer.__srcBitmapData = bitmapData;
|
|
return new Image (buffer);
|
|
|
|
}
|
|
|
|
|
|
public static function fromBytes (bytes:ByteArray, onload:Image -> Void = null):Image {
|
|
|
|
var image = new Image ();
|
|
image.__fromBytes (bytes, onload);
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
public static function fromCanvas (canvas:#if (js && html5) CanvasElement #else Dynamic #end):Image {
|
|
|
|
var buffer = new ImageBuffer (null, canvas.width, canvas.height);
|
|
buffer.src = canvas;
|
|
return new Image (buffer);
|
|
|
|
}
|
|
|
|
|
|
public static function fromFile (path:String, onload:Image -> Void = null, onerror:Void -> Void = null):Image {
|
|
|
|
var image = new Image ();
|
|
image.__fromFile (path, onload, onerror);
|
|
return image;
|
|
|
|
}
|
|
|
|
|
|
public static function fromImageElement (image:#if (js && html5) ImageElement #else Dynamic #end):Image {
|
|
|
|
var buffer = new ImageBuffer (null, image.width, image.height);
|
|
buffer.src = image;
|
|
return new Image (buffer);
|
|
|
|
}
|
|
|
|
|
|
public function getPixel (x:Int, y:Int, format:PixelFormat = null):Int {
|
|
|
|
if (buffer == null || x < 0 || y < 0 || x >= width || y >= height) return 0;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
return ImageCanvasUtil.getPixel (this, x, y, format);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
return ImageDataUtil.getPixel (this, x, y, format);
|
|
|
|
case FLASH:
|
|
|
|
var color = buffer.__srcBitmapData.getPixel (x + offsetX, y + offsetY);
|
|
|
|
if (format == null || format == RGBA) {
|
|
|
|
return ((color & 0xFF) << 24) | (color >> 8);
|
|
|
|
} else {
|
|
|
|
return color;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function getPixel32 (x:Int, y:Int, format:PixelFormat = null):Int {
|
|
|
|
if (buffer == null || x < 0 || y < 0 || x >= width || y >= height) return 0;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
return ImageCanvasUtil.getPixel32 (this, x, y, format);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
return ImageDataUtil.getPixel32 (this, x, y, format);
|
|
|
|
case FLASH:
|
|
|
|
var color = buffer.__srcBitmapData.getPixel32 (x + offsetX, y + offsetY);
|
|
|
|
if (format == null || format == RGBA) {
|
|
|
|
return ((color & 0xFF) << 24) | (color >> 8);
|
|
|
|
} else {
|
|
|
|
return color;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function getPixels (rect:Rectangle, format:PixelFormat = null):ByteArray {
|
|
|
|
if (buffer == null) return null;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
return ImageCanvasUtil.getPixels (this, rect, format);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
return ImageDataUtil.getPixels (this, rect, format);
|
|
|
|
case FLASH:
|
|
|
|
rect.offset (offsetX, offsetY);
|
|
var byteArray = buffer.__srcBitmapData.getPixels (rect.__toFlashRectangle ());
|
|
|
|
if (format == null || format == RGBA) {
|
|
|
|
var color;
|
|
var length = Std.int (byteArray.length / 4);
|
|
|
|
for (i in 0...length) {
|
|
|
|
color = byteArray.readUnsignedInt ();
|
|
byteArray.position -= 4;
|
|
byteArray.writeUnsignedInt (((color & 0xFF) << 24) | (color >> 8));
|
|
|
|
}
|
|
|
|
byteArray.position = 0;
|
|
|
|
}
|
|
|
|
return cast byteArray;
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function merge (sourceImage:Image, sourceRect:Rectangle, destPoint:Vector2, redMultiplier:Int, greenMultiplier:Int, blueMultiplier:Int, alphaMultiplier:Int):Void {
|
|
|
|
if (buffer == null || sourceImage == null) return;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
ImageCanvasUtil.convertToCanvas (this);
|
|
ImageCanvasUtil.merge (this, sourceImage, sourceRect, destPoint, redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
ImageCanvasUtil.convertToData (sourceImage);
|
|
#end
|
|
|
|
ImageDataUtil.merge (this, sourceImage, sourceRect, destPoint, redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier);
|
|
|
|
case FLASH:
|
|
|
|
sourceRect.offset (offsetX, offsetY);
|
|
buffer.__srcBitmapData.merge (sourceImage.buffer.__srcBitmapData, sourceRect.__toFlashRectangle (), destPoint.__toFlashPoint (), redMultiplier, greenMultiplier, blueMultiplier, alphaMultiplier);
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function resize (newWidth:Int, newHeight:Int):Void {
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
ImageCanvasUtil.resize (this, newWidth, newHeight);
|
|
|
|
case DATA:
|
|
|
|
ImageDataUtil.resize (this, newWidth, newHeight);
|
|
|
|
case FLASH:
|
|
|
|
#if flash
|
|
var matrix = new Matrix ();
|
|
matrix.scale (newWidth / buffer.__srcBitmapData.width, newHeight / buffer.__srcBitmapData.height);
|
|
var data = new BitmapData (newWidth, newHeight, true, 0x00FFFFFF);
|
|
data.draw (buffer.__srcBitmapData, matrix, null, null, null, true);
|
|
buffer.__srcBitmapData = data;
|
|
#end
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
buffer.width = newWidth;
|
|
buffer.height = newHeight;
|
|
|
|
offsetX = 0;
|
|
offsetY = 0;
|
|
width = newWidth;
|
|
height = newHeight;
|
|
|
|
}
|
|
|
|
|
|
public function setPixel (x:Int, y:Int, color:Int, format:PixelFormat = null):Void {
|
|
|
|
if (buffer == null || x < 0 || y < 0 || x >= width || y >= height) return;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
ImageCanvasUtil.setPixel (this, x, y, color, format);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
ImageDataUtil.setPixel (this, x, y, color, format);
|
|
|
|
case FLASH:
|
|
|
|
if (format == null || format == RGBA) color = ((color & 0xFF) << 24) | (color >> 8);
|
|
buffer.__srcBitmapData.setPixel (x + offsetX, y + offsetX, color);
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function setPixel32 (x:Int, y:Int, color:Int, format:PixelFormat = null):Void {
|
|
|
|
if (buffer == null || x < 0 || y < 0 || x >= width || y >= height) return;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
ImageCanvasUtil.setPixel32 (this, x, y, color, format);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
ImageDataUtil.setPixel32 (this, x, y, color, format);
|
|
|
|
case FLASH:
|
|
|
|
if (format == null || format == RGBA) color = ((color & 0xFF) << 24) | (color >> 8);
|
|
buffer.__srcBitmapData.setPixel32 (x + offsetX, y + offsetY, color);
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
public function setPixels (rect:Rectangle, byteArray:ByteArray, format:PixelFormat = null):Void {
|
|
|
|
rect = __clipRect (rect);
|
|
if (buffer == null || rect == null) return;
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
ImageCanvasUtil.setPixels (this, rect, byteArray, format);
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
ImageDataUtil.setPixels (this, rect, byteArray, format);
|
|
|
|
case FLASH:
|
|
|
|
rect.offset (offsetX, offsetY);
|
|
if (format == null || format == RGBA) {
|
|
|
|
var srcData = byteArray;
|
|
byteArray = new ByteArray ();
|
|
#if flash
|
|
byteArray.length = srcData.length;
|
|
#end
|
|
|
|
var color;
|
|
var length = Std.int (byteArray.length / 4);
|
|
|
|
for (i in 0...length) {
|
|
|
|
color = srcData.readUnsignedInt ();
|
|
byteArray.writeUnsignedInt (((color & 0xFF) << 24) | (color >> 8));
|
|
|
|
}
|
|
|
|
srcData.position = 0;
|
|
byteArray.position = 0;
|
|
}
|
|
buffer.__srcBitmapData.setPixels (rect.__toFlashRectangle (), byteArray);
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
private static function __base64Encode (bytes:ByteArray):String {
|
|
|
|
#if (js && html5)
|
|
|
|
var extension = switch (bytes.length % 3) {
|
|
|
|
case 1: "==";
|
|
case 2: "=";
|
|
default: "";
|
|
|
|
}
|
|
|
|
if (__base64Encoder == null) {
|
|
|
|
__base64Encoder = new BaseCode (Bytes.ofString (__base64Chars));
|
|
|
|
}
|
|
|
|
return __base64Encoder.encodeBytes (Bytes.ofData (cast bytes.byteView)).toString () + extension;
|
|
|
|
#else
|
|
|
|
return "";
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
private function __clipRect (r:Rectangle):Rectangle {
|
|
|
|
if (r == null) return null;
|
|
|
|
if (r.x < 0) {
|
|
|
|
r.width -= -r.x;
|
|
r.x = 0;
|
|
|
|
if (r.x + r.width <= 0) return null;
|
|
|
|
}
|
|
|
|
if (r.y < 0) {
|
|
|
|
r.height -= -r.y;
|
|
r.y = 0;
|
|
|
|
if (r.y + r.height <= 0) return null;
|
|
|
|
}
|
|
|
|
if (r.x + r.width >= width) {
|
|
|
|
r.width -= r.x + r.width - width;
|
|
|
|
if (r.width <= 0) return null;
|
|
|
|
}
|
|
|
|
if (r.y + r.height >= height) {
|
|
|
|
r.height -= r.y + r.height - height;
|
|
|
|
if (r.height <= 0) return null;
|
|
|
|
}
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
private function __fromBase64 (base64:String, type:String, onload:Image -> Void = null):Void {
|
|
|
|
#if (js && html5)
|
|
var image = new JSImage ();
|
|
|
|
var image_onLoaded = function (event) {
|
|
|
|
buffer = new ImageBuffer (null, image.width, image.height);
|
|
buffer.__srcImage = cast image;
|
|
|
|
offsetX = 0;
|
|
offsetY = 0;
|
|
width = buffer.width;
|
|
height = buffer.height;
|
|
|
|
if (onload != null) {
|
|
|
|
onload (this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
image.addEventListener ("load", image_onLoaded, false);
|
|
image.src = "data:" + type + ";base64," + base64;
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
private function __fromBytes (bytes:ByteArray, onload:Image -> Void):Void {
|
|
|
|
#if (js && html5)
|
|
|
|
var type = "";
|
|
|
|
if (__isPNG (bytes)) {
|
|
|
|
type = "image/png";
|
|
|
|
} else if (__isJPG (bytes)) {
|
|
|
|
type = "image/jpeg";
|
|
|
|
} else if (__isGIF (bytes)) {
|
|
|
|
type = "image/gif";
|
|
|
|
} else {
|
|
|
|
throw "Image tried to read a PNG/JPG ByteArray, but found an invalid header.";
|
|
|
|
}
|
|
|
|
__fromBase64 (__base64Encode (bytes), type, onload);
|
|
|
|
#elseif (cpp || neko || nodejs)
|
|
|
|
var data = lime_image_load (bytes);
|
|
|
|
if (data != null) {
|
|
|
|
__fromImageBuffer (new ImageBuffer (new UInt8Array (data.data), data.width, data.height, data.bpp));
|
|
|
|
if (onload != null) {
|
|
|
|
onload (this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
throw "ImageBuffer.loadFromBytes not supported on this target";
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
private function __fromFile (path:String, onload:Image -> Void, onerror:Void -> Void):Void {
|
|
|
|
#if (js && html5)
|
|
|
|
var image = new JSImage ();
|
|
|
|
image.onload = function (_) {
|
|
|
|
buffer = new ImageBuffer (null, image.width, image.height);
|
|
buffer.__srcImage = cast image;
|
|
|
|
width = image.width;
|
|
height = image.height;
|
|
|
|
if (onload != null) {
|
|
|
|
onload (this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
image.onerror = function (_) {
|
|
|
|
if (onerror != null) {
|
|
|
|
onerror ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
image.src = path;
|
|
|
|
// Another IE9 bug: loading 20+ images fails unless this line is added.
|
|
// (issue #1019768)
|
|
if (image.complete) { }
|
|
|
|
#elseif (cpp || neko || nodejs || java)
|
|
|
|
var buffer = null;
|
|
|
|
if (#if (sys && (!disable_cffi || !format) && !java) true #else false #end && !System.disableCFFI) {
|
|
|
|
var data = lime_image_load (path);
|
|
if (data != null) {
|
|
var ba:ByteArray = cast(data.data, ByteArray);
|
|
#if nodejs
|
|
var u8a = ba.byteView;
|
|
#else
|
|
var u8a = new UInt8Array(ba);
|
|
#end
|
|
buffer = new ImageBuffer (u8a, data.width, data.height, data.bpp);
|
|
}
|
|
|
|
}
|
|
|
|
#if format
|
|
|
|
else {
|
|
|
|
try {
|
|
|
|
var bytes = File.getBytes (path);
|
|
var input = new BytesInput (bytes, 0, bytes.length);
|
|
var png = new Reader (input).read ();
|
|
var data = Tools.extract32 (png);
|
|
var header = Tools.getHeader (png);
|
|
|
|
var data = new UInt8Array (ByteArray.fromBytes (Bytes.ofData (data.getData ())));
|
|
var length = header.width * header.height;
|
|
var b, g, r, a;
|
|
|
|
for (i in 0...length) {
|
|
|
|
var b = data[i * 4];
|
|
var g = data[i * 4 + 1];
|
|
var r = data[i * 4 + 2];
|
|
var a = data[i * 4 + 3];
|
|
|
|
data[i * 4] = r;
|
|
data[i * 4 + 1] = g;
|
|
data[i * 4 + 2] = b;
|
|
data[i * 4 + 3] = a;
|
|
|
|
}
|
|
|
|
buffer = new ImageBuffer (data, header.width, header.height);
|
|
|
|
} catch (e:Dynamic) {}
|
|
|
|
}
|
|
|
|
#end
|
|
|
|
if (buffer != null) {
|
|
|
|
__fromImageBuffer (buffer);
|
|
|
|
if (onload != null) {
|
|
|
|
onload (this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
throw "ImageBuffer.loadFromFile not supported on this target";
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
|
|
private function __fromImageBuffer (buffer:ImageBuffer):Void {
|
|
|
|
this.buffer = buffer;
|
|
|
|
if (buffer != null) {
|
|
|
|
if (width == -1) {
|
|
|
|
this.width = buffer.width;
|
|
|
|
}
|
|
|
|
if (height == -1) {
|
|
|
|
this.height = buffer.height;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
private static function __isJPG (bytes:ByteArray) {
|
|
|
|
bytes.position = 0;
|
|
return bytes.readUnsignedByte () == 0xFF && bytes.readUnsignedByte () == 0xD8;
|
|
|
|
}
|
|
|
|
|
|
private static function __isPNG (bytes:ByteArray) {
|
|
|
|
bytes.position = 0;
|
|
return (bytes.readUnsignedByte () == 0x89 && bytes.readUnsignedByte () == 0x50 && bytes.readUnsignedByte () == 0x4E && bytes.readUnsignedByte () == 0x47 && bytes.readUnsignedByte () == 0x0D && bytes.readUnsignedByte () == 0x0A && bytes.readUnsignedByte () == 0x1A && bytes.readUnsignedByte () == 0x0A);
|
|
|
|
}
|
|
|
|
private static function __isGIF (bytes:ByteArray) {
|
|
|
|
bytes.position = 0;
|
|
|
|
if (bytes.readUnsignedByte () == 0x47 && bytes.readUnsignedByte () == 0x49 && bytes.readUnsignedByte () == 0x46 && bytes.readUnsignedByte () == 0x38) {
|
|
|
|
var b = bytes.readUnsignedByte ();
|
|
return ((b == 0x37 || b == 0x39) && bytes.readUnsignedByte () == 0x61);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get & Set Methods
|
|
|
|
|
|
|
|
|
|
private function get_data ():UInt8Array {
|
|
|
|
if (buffer.data == null && buffer.width > 0 && buffer.height > 0) {
|
|
|
|
#if (js && html5)
|
|
|
|
ImageCanvasUtil.convertToCanvas (this);
|
|
ImageCanvasUtil.sync (this);
|
|
ImageCanvasUtil.createImageData (this);
|
|
|
|
#elseif flash
|
|
|
|
var pixels = buffer.__srcBitmapData.getPixels (buffer.__srcBitmapData.rect);
|
|
buffer.data = new UInt8Array (pixels);
|
|
|
|
#end
|
|
|
|
}
|
|
|
|
return buffer.data;
|
|
|
|
}
|
|
|
|
|
|
private function set_data (value:UInt8Array):UInt8Array {
|
|
|
|
return buffer.data = value;
|
|
|
|
}
|
|
|
|
|
|
private function get_powerOfTwo ():Bool {
|
|
|
|
return ((buffer.width != 0) && ((buffer.width & (~buffer.width + 1)) == buffer.width)) && ((buffer.height != 0) && ((buffer.height & (~buffer.height + 1)) == buffer.height));
|
|
|
|
}
|
|
|
|
|
|
private function set_powerOfTwo (value:Bool):Bool {
|
|
|
|
if (value != powerOfTwo) {
|
|
|
|
var newWidth = 1;
|
|
var newHeight = 1;
|
|
|
|
while (newWidth < buffer.width) {
|
|
|
|
newWidth <<= 1;
|
|
|
|
}
|
|
|
|
while (newHeight < buffer.height) {
|
|
|
|
newHeight <<= 1;
|
|
|
|
}
|
|
|
|
switch (type) {
|
|
|
|
case CANVAS:
|
|
|
|
// TODO
|
|
|
|
case DATA:
|
|
|
|
ImageDataUtil.resizeBuffer (this, newWidth, newHeight);
|
|
|
|
case FLASH:
|
|
|
|
#if flash
|
|
var bitmapData = new BitmapData (newWidth, newHeight, true, 0x000000);
|
|
bitmapData.draw (buffer.src, null, null, null, true);
|
|
|
|
buffer.src = bitmapData;
|
|
buffer.width = newWidth;
|
|
buffer.height = newHeight;
|
|
#end
|
|
|
|
default:
|
|
}
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
private function get_premultiplied ():Bool {
|
|
|
|
return buffer.premultiplied;
|
|
|
|
}
|
|
|
|
|
|
private function set_premultiplied (value:Bool):Bool {
|
|
|
|
if (value && !buffer.premultiplied) {
|
|
|
|
switch (type) {
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
ImageDataUtil.multiplyAlpha (this);
|
|
|
|
default:
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
} else if (!value && buffer.premultiplied) {
|
|
|
|
switch (type) {
|
|
|
|
case DATA:
|
|
|
|
#if (js && html5)
|
|
ImageCanvasUtil.convertToData (this);
|
|
#end
|
|
|
|
ImageDataUtil.unmultiplyAlpha (this);
|
|
|
|
default:
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
public function get_rect ():Rectangle {
|
|
|
|
return new Rectangle (0, 0, width, height);
|
|
|
|
}
|
|
|
|
|
|
public function get_src ():Dynamic {
|
|
|
|
return buffer.src;
|
|
|
|
}
|
|
|
|
|
|
private function set_src (value:Dynamic):Dynamic {
|
|
|
|
return buffer.src = value;
|
|
|
|
}
|
|
|
|
|
|
private function get_transparent ():Bool {
|
|
|
|
if (buffer == null) return false;
|
|
return buffer.transparent;
|
|
|
|
}
|
|
|
|
|
|
private function set_transparent (value:Bool):Bool {
|
|
|
|
// TODO, modify data to set transparency
|
|
if (buffer == null) return false;
|
|
return buffer.transparent = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Native Methods
|
|
|
|
|
|
|
|
|
|
#if (cpp || neko || nodejs)
|
|
private static var lime_image_encode:ImageBuffer -> Int -> Int -> ByteArray = System.load ("lime", "lime_image_encode", 3);
|
|
private static var lime_image_load:Dynamic = System.load ("lime", "lime_image_load", 1);
|
|
#end
|
|
|
|
|
|
} |