Speed up Image loadFromBytes (thanks D-MAN)

This commit is contained in:
Joshua Granick
2019-07-11 09:19:31 -07:00
parent 3de9f59091
commit 66344c8523
2 changed files with 102 additions and 46 deletions

View File

@@ -2,13 +2,16 @@ package lime._internal.backend.html5;
import haxe.io.Bytes; import haxe.io.Bytes;
import js.html.AnchorElement; import js.html.AnchorElement;
import js.html.Blob;
import js.html.ErrorEvent; import js.html.ErrorEvent;
import js.html.Event; import js.html.Event;
import js.html.Image as JSImage; import js.html.Image as JSImage;
import js.html.ProgressEvent; import js.html.ProgressEvent;
import js.html.URL;
import js.html.XMLHttpRequest; import js.html.XMLHttpRequest;
import js.html.XMLHttpRequestResponseType; import js.html.XMLHttpRequestResponseType;
import js.Browser; import js.Browser;
import lime._internal.format.Base64;
import lime.app.Future; import lime.app.Future;
import lime.app.Promise; import lime.app.Promise;
import lime.graphics.Image; import lime.graphics.Image;
@@ -21,6 +24,8 @@ import lime.utils.AssetType;
@:access(lime.graphics.Image) @:access(lime.graphics.Image)
class HTML5HTTPRequest class HTML5HTTPRequest
{ {
private static inline var OPTION_REVOKE_URL:Int = 1 << 0;
private static var activeRequests = 0; private static var activeRequests = 0;
private static var originElement:AnchorElement; private static var originElement:AnchorElement;
private static var originHostname:String; private static var originHostname:String;
@@ -172,7 +177,8 @@ class HTML5HTTPRequest
instance: this, instance: this,
uri: uri, uri: uri,
promise: promise, promise: promise,
type: AssetType.BINARY type: AssetType.BINARY,
options: 0
}); });
} }
@@ -186,7 +192,7 @@ class HTML5HTTPRequest
if (activeRequests < requestLimit) if (activeRequests < requestLimit)
{ {
activeRequests++; activeRequests++;
__loadImage(uri, promise); __loadImage(uri, promise, 0);
} }
else else
{ {
@@ -195,13 +201,46 @@ class HTML5HTTPRequest
instance: null, instance: null,
uri: uri, uri: uri,
promise: promise, promise: promise,
type: AssetType.IMAGE type: AssetType.IMAGE,
options: 0
}); });
} }
return promise.future; return promise.future;
} }
private static function loadImageFromBytes(bytes:Bytes, type:String):Future<Image>
{
var uri = __createBlobURIFromBytes(bytes, type);
if (uri != null)
{
var promise = new Promise<Image>();
if (activeRequests < requestLimit)
{
activeRequests++;
__loadImage(uri, promise, OPTION_REVOKE_URL);
}
else
{
requestQueue.add(
{
instance: null,
uri: uri,
promise: promise,
type: AssetType.IMAGE,
options: OPTION_REVOKE_URL
});
}
return promise.future;
}
else
{
return loadImage("data:" + type + ";base64," + Base64.encode(bytes));
}
}
public function loadText(uri:String):Future<String> public function loadText(uri:String):Future<String>
{ {
var promise = new Promise<String>(); var promise = new Promise<String>();
@@ -218,7 +257,8 @@ class HTML5HTTPRequest
instance: this, instance: this,
uri: uri, uri: uri,
promise: promise, promise: promise,
type: AssetType.TEXT type: AssetType.TEXT,
options: 0
}); });
} }
@@ -236,7 +276,7 @@ class HTML5HTTPRequest
switch (queueItem.type) switch (queueItem.type)
{ {
case IMAGE: case IMAGE:
__loadImage(queueItem.uri, queueItem.promise); __loadImage(queueItem.uri, queueItem.promise, queueItem.options);
case TEXT: case TEXT:
queueItem.instance.__loadText(queueItem.uri, queueItem.promise); queueItem.instance.__loadText(queueItem.uri, queueItem.promise);
@@ -272,6 +312,11 @@ class HTML5HTTPRequest
parent.responseStatus = request.status; parent.responseStatus = request.status;
} }
private static inline function __createBlobURIFromBytes(bytes:Bytes, type:String):String
{
return URL.createObjectURL(new Blob([bytes.getData()], {type: type}));
}
private static function __fixHostname(hostname:String):String private static function __fixHostname(hostname:String):String
{ {
return hostname == null ? "" : hostname; return hostname == null ? "" : hostname;
@@ -301,10 +346,15 @@ class HTML5HTTPRequest
return (protocol == null || protocol == "") ? "http:" : protocol; return (protocol == null || protocol == "") ? "http:" : protocol;
} }
private static function __isInMemoryURI(uri:String):Bool
{
return StringTools.startsWith(uri, "data:") || StringTools.startsWith(uri, "blob:");
}
private static function __isSameOrigin(path:String):Bool private static function __isSameOrigin(path:String):Bool
{ {
if (path == null || path == "") return true; if (path == null || path == "") return true;
if (StringTools.startsWith(path, "data:")) return true; if (__isInMemoryURI(path)) return true;
if (originElement == null) if (originElement == null)
{ {
@@ -380,7 +430,7 @@ class HTML5HTTPRequest
load(uri, progress, readyStateChange); load(uri, progress, readyStateChange);
} }
private static function __loadImage(uri:String, promise:Promise<Image>):Void private static function __loadImage(uri:String, promise:Promise<Image>, options:Int):Void
{ {
var image = new JSImage(); var image = new JSImage();
@@ -394,10 +444,11 @@ class HTML5HTTPRequest
supportsImageProgress = untyped __js__("'onprogress' in image"); supportsImageProgress = untyped __js__("'onprogress' in image");
} }
if (supportsImageProgress || StringTools.startsWith(uri, "data:")) if (supportsImageProgress || __isInMemoryURI(uri))
{ {
image.addEventListener("load", function(event) image.addEventListener("load", function(event)
{ {
__revokeBlobURI(uri, options);
var buffer = new ImageBuffer(null, image.width, image.height); var buffer = new ImageBuffer(null, image.width, image.height);
buffer.__srcImage = cast image; buffer.__srcImage = cast image;
@@ -414,6 +465,8 @@ class HTML5HTTPRequest
image.addEventListener("error", function(event) image.addEventListener("error", function(event)
{ {
__revokeBlobURI(uri, options);
activeRequests--; activeRequests--;
processQueue(); processQueue();
@@ -489,6 +542,14 @@ class HTML5HTTPRequest
binary = false; binary = false;
load(uri, progress, readyStateChange); load(uri, progress, readyStateChange);
} }
private static function __revokeBlobURI(uri:String, options:Int):Void
{
if ((options & OPTION_REVOKE_URL) != 0)
{
URL.revokeObjectURL(uri);
}
}
} }
@:dox(hide) typedef QueueItem = @:dox(hide) typedef QueueItem =
@@ -497,4 +558,5 @@ class HTML5HTTPRequest
var type:AssetType; var type:AssetType;
var promise:Dynamic; var promise:Dynamic;
var uri:String; var uri:String;
var options:Int;
} }

View File

@@ -387,7 +387,8 @@ class Image
sourceRect.offset(sourceImage.offsetX, sourceImage.offsetY); sourceRect.offset(sourceImage.offsetX, sourceImage.offsetY);
destPoint.offset(offsetX, offsetY); destPoint.offset(offsetX, offsetY);
buffer.__srcBitmapData.copyChannel(sourceImage.buffer.src, sourceRect.__toFlashRectangle(), destPoint.__toFlashPoint(), srcChannel, dstChannel); buffer.__srcBitmapData.copyChannel(sourceImage.buffer.src, sourceRect.__toFlashRectangle(), destPoint.__toFlashPoint(), srcChannel,
dstChannel);
default: default:
} }
@@ -616,20 +617,16 @@ class Image
@param bitmapData A source `bitmapData` to use @param bitmapData A source `bitmapData` to use
@return A new `Image` instance @return A new `Image` instance
**/ **/
#if flash public static function fromBitmapData(bitmapData:#if flash BitmapData #else Dynamic #end):Image
public static function fromBitmapData(bitmapData:BitmapData):Image { {
#else if (bitmapData == null) return null;
public static function fromBitmapData(bitmapData:Dynamic):Image { #if flash
#end var buffer = new ImageBuffer(null, bitmapData.width, bitmapData.height);
if (bitmapData == null) return null; buffer.__srcBitmapData = bitmapData;
#if flash return new Image(buffer);
var buffer = new ImageBuffer(null, bitmapData.width, bitmapData.height); #else
return bitmapData.image;
buffer.__srcBitmapData = bitmapData; #end
return new Image(buffer);
#else
return bitmapData.image;
#end
} }
#end #end
@@ -664,18 +661,15 @@ class Image
@param canvas A `CanvasElement` @param canvas A `CanvasElement`
@return A new `Image` instance @return A new `Image` instance
**/ **/
#if (js && html5) public static function fromCanvas(canvas:#if (js && html5) CanvasElement #else Dynamic #end):Image
public static function fromCanvas(canvas:CanvasElement):Image { {
#else if (canvas == null) return null;
public static function fromCanvas(canvas:Dynamic):Image { var buffer = new ImageBuffer(null, canvas.width, canvas.height);
#end buffer.src = canvas;
if (canvas == null) return null; var image = new Image(buffer);
var buffer = new ImageBuffer(null, canvas.width, canvas.height);
buffer.src = canvas;
var image = new Image(buffer);
image.type = CANVAS; image.type = CANVAS;
return image; return image;
} }
#end #end
@@ -710,18 +704,15 @@ class Image
@param image An `ImageElement` instance @param image An `ImageElement` instance
@return A new `Image` instance @return A new `Image` instance
**/ **/
#if (js && html5) public static function fromImageElement(image:#if (js && html5) ImageElement #else Dynamic #end):Image
public static function fromImageElement(image:ImageElement):Image { {
#else if (image == null) return null;
public static function fromImageElement(image:Dynamic):Image { var buffer = new ImageBuffer(null, image.width, image.height);
#end buffer.src = image;
if (image == null) return null; var _image = new Image(buffer);
var buffer = new ImageBuffer(null, image.width, image.height);
buffer.src = image;
var _image = new Image(buffer);
_image.type = CANVAS; _image.type = CANVAS;
return _image; return _image;
} }
#end #end
@@ -967,8 +958,11 @@ class Image
// throw "Image tried to read PNG/JPG Bytes, but found an invalid header."; // throw "Image tried to read PNG/JPG Bytes, but found an invalid header.";
return Future.withValue(null); return Future.withValue(null);
} }
#if !display
return HTML5HTTPRequest.loadImageFromBytes(bytes, type);
#else
return loadFromBase64(Base64.encode(bytes), type); return loadFromBase64(Base64.encode(bytes), type);
#end
#elseif flash #elseif flash
var promise = new Promise<Image>(); var promise = new Promise<Image>();