mirror of
https://github.com/QuiteAFancyEmerald/Holy-Unblocker.git
synced 2025-05-14 12:20:02 -04:00
461 lines
No EOL
12 KiB
JavaScript
461 lines
No EOL
12 KiB
JavaScript
/* downscale 1.0.5
|
|
Better image downscale with canvas.
|
|
https://github.com/ytiurin/downscale
|
|
Eugene Tiurin <yevhentiurin@gmail.com>
|
|
Under MIT license
|
|
*/
|
|
(function (root, factory) {
|
|
if (typeof define === 'function' && define.amd) {
|
|
define([], factory);
|
|
} else if (typeof module === 'object' && module.exports) {
|
|
module.exports = factory();
|
|
} else {
|
|
root.downscale = factory();
|
|
}
|
|
}(this, function () {
|
|
|
|
function resizeWithCanvas(canvas, source, destWidth, destHeight, sourceX,
|
|
sourceY, sourceWidth, sourceHeight)
|
|
{
|
|
var canvas = document.createElement("canvas")
|
|
|
|
canvas.width = destWidth
|
|
canvas.height = destHeight
|
|
|
|
var ctx = canvas.getContext("2d")
|
|
|
|
ctx.mozImageSmoothingEnabled = true
|
|
ctx.imageSmoothingQuality = "high"
|
|
ctx.webkitImageSmoothingEnabled = true
|
|
ctx.msImageSmoothingEnabled = true
|
|
ctx.imageSmoothingEnabled = true
|
|
|
|
ctx.drawImage(source,
|
|
sourceX, sourceY, sourceWidth, sourceHeight,
|
|
0, 0, destWidth, destHeight)
|
|
|
|
return canvas
|
|
}
|
|
function round(val)
|
|
{
|
|
return (val + 0.49) << 0
|
|
}
|
|
|
|
function downsample(sourceImageData, destWidth, destHeight, sourceX, sourceY,
|
|
sourceWidth, sourceHeight)
|
|
{
|
|
var dest = new ImageData(destWidth, destHeight)
|
|
|
|
var SOURCE_DATA = new Int32Array(sourceImageData.data.buffer)
|
|
var SOURCE_WIDTH = sourceImageData.width
|
|
|
|
var DEST_DATA = new Int32Array(dest.data.buffer)
|
|
var DEST_WIDTH = dest.width
|
|
|
|
var SCALE_FACTOR_X = destWidth / sourceWidth
|
|
var SCALE_FACTOR_Y = destHeight / sourceHeight
|
|
var SCALE_RANGE_X = round(1 / SCALE_FACTOR_X)
|
|
var SCALE_RANGE_Y = round(1 / SCALE_FACTOR_Y)
|
|
var SCALE_RANGE_SQR = SCALE_RANGE_X * SCALE_RANGE_Y
|
|
|
|
for (var destRow = 0; destRow < dest.height; destRow++) {
|
|
for (var destCol = 0; destCol < DEST_WIDTH; destCol++) {
|
|
|
|
var sourceInd = sourceX + round(destCol / SCALE_FACTOR_X) +
|
|
(sourceY + round(destRow / SCALE_FACTOR_Y)) * SOURCE_WIDTH
|
|
|
|
var destRed = 0
|
|
var destGreen = 0
|
|
var destBlue = 0
|
|
var destAlpha = 0
|
|
|
|
for (var sourceRow = 0; sourceRow < SCALE_RANGE_Y; sourceRow++)
|
|
for (var sourceCol = 0; sourceCol < SCALE_RANGE_X; sourceCol++) {
|
|
var sourcePx = SOURCE_DATA[sourceInd + sourceCol + sourceRow * SOURCE_WIDTH]
|
|
destRed += sourcePx << 24 >>> 24
|
|
destGreen += sourcePx << 16 >>> 24
|
|
destBlue += sourcePx << 8 >>> 24
|
|
destAlpha += sourcePx >>> 24
|
|
}
|
|
|
|
destRed = round(destRed / SCALE_RANGE_SQR)
|
|
destGreen = round(destGreen / SCALE_RANGE_SQR)
|
|
destBlue = round(destBlue / SCALE_RANGE_SQR)
|
|
destAlpha = round(destAlpha / SCALE_RANGE_SQR)
|
|
|
|
DEST_DATA[destCol + destRow * DEST_WIDTH] =
|
|
(destAlpha << 24) |
|
|
(destBlue << 16) |
|
|
(destGreen << 8) |
|
|
(destRed)
|
|
}
|
|
}
|
|
|
|
return dest
|
|
}
|
|
function createTiming(enabled, source, destWidth, destHeight)
|
|
{
|
|
var start = new Date
|
|
var timing = {}
|
|
var prev = start
|
|
var n = "01"
|
|
|
|
return {
|
|
mark: enabled ? function(name) {
|
|
name = n + ". " + (name || "...")
|
|
timing[name] = { "time (ms)": (new Date) - prev }
|
|
prev = new Date
|
|
n = ("0" + ((n >> 0) + 1)).substr(-2)
|
|
} : new Function,
|
|
finish: enabled ? function() {
|
|
timing[n + " TOTAL"] = { "time (ms)": (new Date) - start }
|
|
console.log("IMAGE SOURCE:", source)
|
|
console.log("DOWNSCALE TO:", destWidth + "x" + destHeight)
|
|
console.table(timing)
|
|
} : new Function
|
|
}
|
|
}
|
|
|
|
function createCache()
|
|
{
|
|
var keys = []
|
|
var values = []
|
|
var subscribes = []
|
|
var PENDING = new Object
|
|
|
|
return {
|
|
createSetter: function(key) {
|
|
if (this.get(key)) {
|
|
return
|
|
}
|
|
var cacheInd = keys.push(key) - 1
|
|
values.push(PENDING)
|
|
subscribes.push([])
|
|
|
|
return function(value) {
|
|
values[cacheInd] = value
|
|
subscribes[cacheInd] = subscribes[cacheInd].reduce(function(r, resolve) {
|
|
resolve(value)
|
|
}, [])
|
|
}
|
|
},
|
|
get: function(key, resolve) {
|
|
var cacheInd = keys.indexOf(key)
|
|
if (!~cacheInd) {
|
|
return
|
|
}
|
|
if (values[cacheInd] === PENDING) {
|
|
subscribes[cacheInd].push(resolve)
|
|
return
|
|
}
|
|
resolve(values[cacheInd])
|
|
},
|
|
has: function(key) {
|
|
return !!~keys.indexOf(key)
|
|
}
|
|
}
|
|
}
|
|
|
|
function getImageData(canvas, img, sourceWidth, sourceHeight)
|
|
{
|
|
canvas.width = sourceWidth
|
|
canvas.height = sourceHeight
|
|
|
|
var ctx = canvas.getContext("2d")
|
|
ctx.drawImage(img, 0, 0)
|
|
|
|
return ctx.getImageData(0, 0, sourceWidth, sourceHeight)
|
|
}
|
|
|
|
function putImageData(canvas, imageData)
|
|
{
|
|
canvas.width = imageData.width
|
|
canvas.height = imageData.height
|
|
|
|
var ctx = canvas.getContext("2d")
|
|
ctx.putImageData(imageData, 0, 0)
|
|
|
|
return canvas
|
|
}
|
|
|
|
function remapDimensions(destWidth, destHeight, sourceX, sourceY, sourceWidth,
|
|
sourceHeight)
|
|
{
|
|
var origSourceWidth = sourceWidth
|
|
var origSourceHeight = sourceHeight
|
|
|
|
var sourceRatio = sourceWidth / sourceHeight
|
|
|
|
if (destWidth === 0) {
|
|
destWidth = destHeight * sourceRatio >> 0
|
|
}
|
|
|
|
if (destHeight === 0) {
|
|
destHeight = destWidth / sourceRatio >> 0
|
|
}
|
|
|
|
var destRatio = destWidth / destHeight
|
|
|
|
if (destRatio > sourceRatio) {
|
|
sourceHeight = sourceWidth / destRatio >> 0
|
|
}
|
|
else {
|
|
sourceWidth = sourceHeight * destRatio >> 0
|
|
}
|
|
|
|
var sourceX = sourceX || (origSourceWidth - sourceWidth) / 2 >> 0
|
|
var sourceY = sourceY || (origSourceHeight - sourceHeight) / 2 >> 0
|
|
|
|
return {
|
|
destWidth : destWidth,
|
|
destHeight : destHeight,
|
|
sourceX : sourceX,
|
|
sourceY : sourceY,
|
|
sourceWidth : sourceWidth,
|
|
sourceHeight : sourceHeight
|
|
}
|
|
}
|
|
|
|
function produceResult(canvas, options, callback)
|
|
{
|
|
if (options.returnCanvas) {
|
|
callback(canvas)
|
|
return
|
|
}
|
|
|
|
if (options.returnBlob) {
|
|
canvas.toBlob(callback, "image/" + (options.imageType || "jpeg"),
|
|
options.quality || .85)
|
|
return
|
|
}
|
|
|
|
var dataURL = canvas.toDataURL("image/" + (options.imageType || "jpeg"),
|
|
options.quality || .85)
|
|
|
|
callback(dataURL)
|
|
}
|
|
|
|
function loadArrayBuffer(source, callback)
|
|
{
|
|
var xhr = new XMLHttpRequest
|
|
|
|
xhr.open("GET", source)
|
|
xhr.responseType = "arraybuffer"
|
|
|
|
xhr.addEventListener("load", function() {
|
|
callback(this.response)
|
|
})
|
|
|
|
xhr.send()
|
|
}
|
|
|
|
function loadImg(img, callback)
|
|
{
|
|
if (img.complete) {
|
|
callback()
|
|
}
|
|
else {
|
|
img.addEventListener("load", callback)
|
|
}
|
|
}
|
|
|
|
function loadVideo(video, callback)
|
|
{
|
|
if (video.readyState > 1) {
|
|
callback()
|
|
}
|
|
else {
|
|
video.addEventListener("loadeddata", callback)
|
|
}
|
|
}
|
|
|
|
function detectSourceType(source)
|
|
{
|
|
if (source instanceof File) {
|
|
return "File"
|
|
}
|
|
if (source instanceof HTMLImageElement) {
|
|
return "HTMLImageElement"
|
|
}
|
|
if (source instanceof HTMLVideoElement) {
|
|
return "HTMLVideoElement"
|
|
}
|
|
if (typeof source === "string") {
|
|
return "URL"
|
|
}
|
|
}
|
|
|
|
function validateArguments(args)
|
|
{
|
|
if (args.length < 3) {
|
|
return new TypeError("3 arguments required, but only " + args.length + " present.")
|
|
}
|
|
if (!detectSourceType(args[0])) {
|
|
return new TypeError("First argument should be HTMLImageElement, HTMLVideoElement, File of String")
|
|
}
|
|
if (typeof args[1] !== "number") {
|
|
return new TypeError("Second argument should be a number")
|
|
}
|
|
if (typeof args[2] !== "number") {
|
|
return new TypeError("Third argument should be a number")
|
|
}
|
|
}
|
|
|
|
function downscale(source, destWidth, destHeight, options)
|
|
{
|
|
var timing = createTiming(options && options.debug || false,
|
|
source, destWidth, destHeight)
|
|
|
|
var err = validateArguments(arguments)
|
|
if (err instanceof TypeError) {
|
|
return Promise.reject(err)
|
|
}
|
|
|
|
options = options || {}
|
|
|
|
var resolveResult, rejectResult
|
|
var result = new Promise(function(resolve, reject) {
|
|
resolveResult = resolve
|
|
rejectResult = reject
|
|
})
|
|
|
|
downscale.canvas = downscale.canvas || document.createElement("canvas")
|
|
downscale.cache = downscale.cache || createCache()
|
|
|
|
var canvas = downscale.canvas
|
|
var cache = downscale.cache
|
|
|
|
if (cache.has(source)) {
|
|
timing.mark()
|
|
cache.get(source,
|
|
function(cacheData) {
|
|
timing.mark("PENDING CACHE")
|
|
var img = cacheData[0]
|
|
var imageData = cacheData[1]
|
|
|
|
var dims = remapDimensions(destWidth, destHeight, options.sourceX,
|
|
options.sourceY, imageData.width, imageData.height)
|
|
|
|
if (dims.sourceWidth / dims.destWidth >= 2 &&
|
|
dims.sourceHeight / dims.destHeight >= 2) {
|
|
timing.mark()
|
|
var destImageData = downsample(imageData, dims.destWidth,
|
|
dims.destHeight, dims.sourceX, dims.sourceY, dims.sourceWidth,
|
|
dims.sourceHeight)
|
|
timing.mark("DOWNSCALE")
|
|
|
|
canvas = putImageData(canvas, destImageData)
|
|
}
|
|
else {
|
|
canvas = resizeWithCanvas(canvas, img, dims.destWidth,
|
|
dims.destHeight, dims.sourceX, dims.sourceY, dims.sourceWidth,
|
|
dims.sourceHeight)
|
|
timing.mark("RESIZE WITH CANVAS")
|
|
}
|
|
|
|
produceResult(canvas, options,
|
|
function(result) {
|
|
timing.mark("PRODUCE RESULT")
|
|
resolveResult(result)
|
|
timing.finish()
|
|
})
|
|
})
|
|
|
|
return result
|
|
}
|
|
|
|
var scaleSourceResolve = function(source, width, height) {
|
|
var dims = remapDimensions(destWidth, destHeight, options.sourceX,
|
|
options.sourceY, width, height)
|
|
|
|
if (dims.sourceWidth / dims.destWidth >= 2 &&
|
|
dims.sourceHeight / dims.destHeight >= 2) {
|
|
timing.mark()
|
|
var imageData = getImageData(canvas, source, width, height)
|
|
timing.mark("GET IMAGE DATA")
|
|
|
|
var destImageData = downsample(imageData, dims.destWidth,
|
|
dims.destHeight, dims.sourceX, dims.sourceY, dims.sourceWidth,
|
|
dims.sourceHeight)
|
|
timing.mark("DOWNSCALE")
|
|
|
|
canvas = putImageData(canvas, destImageData)
|
|
|
|
if (detectSourceType(source) !== "HTMLVideoElement") {
|
|
cache.createSetter(source)([source, imageData]);
|
|
}
|
|
}
|
|
else {
|
|
canvas = resizeWithCanvas(canvas, source, dims.destWidth,
|
|
dims.destHeight, dims.sourceX, dims.sourceY, dims.sourceWidth,
|
|
dims.sourceHeight)
|
|
timing.mark("RESIZE WITH CANVAS")
|
|
}
|
|
|
|
produceResult(canvas, options,
|
|
function(result) {
|
|
timing.mark("PRODUCE RESULT")
|
|
resolveResult(result)
|
|
timing.finish()
|
|
})
|
|
}
|
|
|
|
var URL = window.URL || window.webkitURL
|
|
|
|
switch (detectSourceType(source)) {
|
|
|
|
case "File":
|
|
var sourceImg = document.createElement("img")
|
|
timing.mark()
|
|
sourceImg.src = URL.createObjectURL(source)
|
|
timing.mark("READ FILE")
|
|
loadImg(sourceImg,
|
|
function() {
|
|
timing.mark("LOAD IMAGE")
|
|
scaleSourceResolve(sourceImg, sourceImg.naturalWidth,
|
|
sourceImg.naturalHeight)
|
|
})
|
|
break
|
|
|
|
case "HTMLImageElement":
|
|
timing.mark()
|
|
loadImg(source,
|
|
function() {
|
|
timing.mark("LOAD IMAGE")
|
|
scaleSourceResolve(source, source.naturalWidth, source.naturalHeight)
|
|
})
|
|
break
|
|
|
|
case "HTMLVideoElement":
|
|
loadVideo(source,
|
|
function() {
|
|
scaleSourceResolve(source, source.videoWidth, source.videoHeight)
|
|
})
|
|
break
|
|
|
|
case "URL":
|
|
timing.mark()
|
|
setTimeout(function() {
|
|
loadArrayBuffer(source,
|
|
function(arrayBuffer) {
|
|
timing.mark("LOAD ARRAY BUFFER")
|
|
var arrayBufferView = new Uint8Array(arrayBuffer)
|
|
var blob = new Blob( [ arrayBufferView ], { type: "image/jpeg" } )
|
|
var sourceImg = document.createElement("img")
|
|
sourceImg.src = URL.createObjectURL(blob)
|
|
timing.mark()
|
|
loadImg(sourceImg,
|
|
function() {
|
|
timing.mark("LOAD IMAGE")
|
|
scaleSourceResolve(sourceImg, sourceImg.naturalWidth,
|
|
sourceImg.naturalHeight)
|
|
})
|
|
})
|
|
})
|
|
break
|
|
}
|
|
|
|
return result
|
|
}
|
|
return downscale
|
|
})); |