Client PNG compression using javascript like pngcrush? - javascript

I'm developing HTML5 apps.
When user uploads image from their mobile, the size was too large.
I want to compress the image as PNG like the pngcrush way.
Is there any good way to choice on the frontend (like a javascript library)?
Or is it possible to port the pngcrush library to javascript?

There are a few projects out there which seem to be based around the idea of using emscripten (a LLVM-to-JavaScript compiler) to actually compile the source code from pngcrush to working JavaScript for the browser.
JavaScript-Packer/PNGCrush.html - based on pngcrush-1.7.27
richardassar/pngcrush.js - based on pngcrush-1.7.27
pngcrush-crushed - based on pngcrush-1.7.58
The version for pngcrush-1.7.27 is currently the only one that doesn't seem to produce corrupted images for me. I put together an example which uses promises here: http://plnkr.co/edit/iLpbOjlYiacR04oGdXSI?p=preview
Here's a basic usage example:
var instance = new pngcrush();
instance.exec(inputFile, function (stdoutEvent) {
console.log(stdoutEvent.data.line);
}).then(function (doneEvent) {
var outputFile = new Blob([doneEvent.data.data], { type: 'image/png' });
// do something with the outputFile
});
Here are the contents of the pngcrush-class.js file from the above plunker for reference:
(function(exports) {
var noop = function () {};
function pngcrush () {
this.callbacks = {
'error': [],
'done': [],
'start': [],
'stdout': []
};
}
pngcrush.prototype.exec = function (file, notify) {
var self = this;
if (this.execPromise) {
return this.execPromise.catch(noop).then(function () {
return self.exec(file, notify);
});
}
if (file.type !== 'image/png') {
return Promise.reject(file);
}
var promise = this.execPromise = this.readAsArrayBuffer(file).then(function (event) {
var arrayBuffer = event.target.result;
return self.initWorker().then(function (worker) {
var done = new Promise(function (resolve, reject) {
var offDone, offError, offStdout;
offDone = self.once('done', function (event) {
offError();
offStdout();
resolve(event);
});
offError = self.once('error', function (event) {
offDone();
offStdout();
reject(event);
});
offStdout = self.on('stdout', function (event) {
if (typeof notify === 'function') {
notify.call(self, event);
}
});
worker.postMessage({
'type': 'file',
'data': new Uint8Array(arrayBuffer)
});
worker.postMessage({
'type': 'command',
'command': 'go'
});
});
done.catch(noop).then(function () {
worker.terminate();
});
return done;
});
});
promise.catch(noop).then(function () {
if (promise === self.execPromise) {
delete self.execPromise;
}
});
return promise;
};
pngcrush.prototype.initWorker = function () {
var self = this;
if (this.workerPromise) {
return this.workerPromise;
}
var promise = this.workerPromise = new Promise(function (resolve, reject) {
var worker = new Worker('worker.js');
worker.onerror = function (event) {
var callbacks = [];
reject(event);
Array.prototype.push.apply(callbacks, self.callbacks.error);
while (callbacks.length) {
callbacks.shift().call(self, event);
}
};
worker.onmessage = function (event) {
if (event.data.type === 'ready') {
worker.onmessage = function (event) {
var name = event.data.type;
if (typeof self.callbacks[name] !== 'undefined') {
var callbacks = [];
Array.prototype.push.apply(callbacks, self.callbacks[name]);
while (callbacks.length) {
callbacks.shift().call(self, event);
}
}
};
resolve(worker);
}
};
});
promise.catch(noop).then(function () {
if (promise === self.workerPromise) {
delete self.workerPromise;
}
});
return promise;
};
pngcrush.prototype.on = function (name, callback) {
var self = this;
if (typeof this.callbacks[name] !== 'undefined' && typeof callback === 'function') {
this.callbacks[name].push(callback);
var off = (function () {
var ran = false;
return function () {
if (ran === true) {
return;
}
ran = true;
var idx = self.callbacks[name].lastIndexOf(callback);
if (idx !== -1) {
self.callbacks[name].splice(idx - 1, 1);
}
};
})();
return off;
}
return noop;
};
pngcrush.prototype.once = function (name, callback) {
var off = this.on(name, function () {
off();
callback.apply(this, arguments);
});
return off;
};
pngcrush.prototype.readAsArrayBuffer = function (file) {
var fileReader = new FileReader();
return new Promise(function (resolve, reject) {
fileReader.onerror = reject;
fileReader.onload = resolve;
fileReader.readAsArrayBuffer(file);
});
};
pngcrush.prototype.readAsDataURL = function (file) {
var fileReader = new FileReader();
return new Promise(function (resolve, reject) {
fileReader.onerror = reject;
fileReader.onload = resolve;
fileReader.readAsDataURL(file);
});
};
exports.pngcrush = pngcrush;
})(this);

Related

Class constructor property value is null

I am trying to use indexedDB for the first time and want to create a class for it. My code works opening the database and saving an object to it but when I try to retrieve the object it is null
var libraries = new storedLibraries();
libraries.openDb();
```
user action
```
libraries.saveLibrary(template);
Class.
class storedLibraries {
constructor() {
this.DB_NAME = 'recording-template';
this.DB_VERSION = 1;
this.DB_STORE_NAME = 'library';
this.db = null;
this.result = null;
if (!('indexedDB' in window)) {
console.log('This browser doesn\'t support IndexedDB');
return;
}
}
openDb() {
var request = indexedDB.open(this.DB_NAME, this.DB_VERSION);
var _self = this;
request.onsuccess = function (evt) {
_self.db = this.result;
_self.getObjectStore();
console.log(_self.result);
console.log("openDb DONE");
};
request.onerror = function (evt) {
console.error("openDb:", evt.target.errorCode);
};
request.onupgradeneeded = function (evt) {
console.log("openDb.onupgradeneeded");
var store = evt.currentTarget.result.createObjectStore(_self.DB_STORE_NAME, { keyPath: 'id', autoIncrement: true });
};
}
saveLibrary(template) {
var transaction = this.db.transaction(["library"], "readwrite");
// Do something when all the data is added to the database.
var _self = this;
transaction.oncomplete = function(event) {
console.log("All done!");
};
transaction.onerror = function(event) {
// Don't forget to handle errors!
};
var objectStore = transaction.objectStore("library");
var request = objectStore.add(template);
request.onsuccess = function(event) {
// event.target.result
};
}
getObjectStore() {
//console.log(this);
var transaction = this.db.transaction(["library"], "readwrite");
var objectStore = transaction.objectStore("library");
var request = objectStore.getAll();
var _self = this;
request.onerror = function(event) {
// Handle errors!
};
request.onsuccess = function(event) {
// Do something with the request.result!
_self.result = request.result;
console.log(_self.result);
};
}
}
The console.log(_self.result); in getObjectStore() outputs the correct value but in openDb() it is null. I have tried many different things but I am obviously not understanding something?
I got it to work using promises.
const libraries = new storedLibraries();
libraries.loadLibraries();
Class.
class storedLibraries {
constructor() {
this.DB_NAME = 'recording-template';
this.DB_VERSION = 1;
this.DB_STORE_NAME = 'library';
this.db = null;
this.libraries = [];
if (!('indexedDB' in window)) {
console.log('This browser doesn\'t support IndexedDB');
return;
}
}
loadLibraries() {
this.openDb().then((result) => {
this.getObjectStore().then((result) => {
for(const res of result) {
this.libraries.push(new recordingsTemplate(res));
}
});
}).catch(console.error);
}
async openDb() {
var _self = this;
const prom = new Promise((resolve, reject) => {
var request = indexedDB.open(_self.DB_NAME, _self.DB_VERSION);
request.onsuccess = (event) => {
console.log("openDb:");
_self.db = request.result;
resolve(request.result);
}
request.onerror = (event) => {
console.error("openDb:", event.target.errorCode);
reject(evt);
}
request.onupgradeneeded = (event) => {
console.log("openDb.onupgradeneeded");
_self.db = request.result;
var store = event.currentTarget.result.createObjectStore(_self.DB_STORE_NAME, { keyPath: 'id', autoIncrement: true });
var transaction = event.target.transaction;
transaction.oncomplete = (event) => {
resolve(store);
}
}
});
return prom;
}
async getObjectStore() {
var _self = this;
const prom = new Promise((resolve, reject) => {
var transaction = _self.db.transaction(["library"], "readwrite");
var objectStore = transaction.objectStore("library");
var request = objectStore.getAll();
request.onerror = (event) => {
// Handle errors!
reject(event);
};
request.onsuccess = (event) => {
resolve(request.result);
};
});
return prom;
}
async saveLibrary(template) {
var _self = this;
const prom = new Promise((resolve, reject) => {
var transaction = _self.db.transaction(["library"], "readwrite");
// Do something when all the data is added to the database.
transaction.oncomplete = function(event) {
console.log("All done!");
};
transaction.onerror = function(event) {
// Don't forget to handle errors!
};
var objectStore = transaction.objectStore("library");
var request = objectStore.add(template);
request.onsuccess = function(event) {
// event.target.result
};
});
return prom;
}
}

Ping site with knockout.js

I want to ping a few sites using javascript, and found this pen and does what I want.
However, I don't understand when I add goo12121212gle.com to the list of sites as a test it comes up saying that the domain has responded but in the console log I see ERR_NAME_NOT_RESOLVED??
I am new to JS but I am not sure why the below script is both saying the site is there and not at the same time? Is something missing from the script?
function ping(ip, callback) {
if (!this.inUse) {
this.status = 'unchecked';
this.inUse = true;
this.callback = callback;
this.ip = ip;
var _that = this;
this.img = new Image();
this.img.onload = function () {
_that.inUse = false;
_that.callback('online');
};
this.img.onerror = function (e) {
if (_that.inUse) {
_that.inUse = false;
_that.callback('offline', e);
}
};
this.start = new Date().getTime();
this.img.src = "http://" + ip;
this.timer = setTimeout(function () {
if (_that.inUse) {
_that.inUse = false;
_that.callback('timeout');
}
}, 1500);
}
}
var PingModel = function (servers) {
var self = this;
var myServers = [];
ko.utils.arrayForEach(servers, function (location) {
myServers.push({
name: location,
status: ko.observable('unchecked')
});
});
self.servers = ko.observableArray(myServers);
ko.utils.arrayForEach(self.servers(), function (s) {
s.status('checking');
new ping(s.name, function (status, e) {
s.status(e ? "error" : status);
});
});
};
var komodel = new PingModel(['goo12121212gle.com','msn.com','104.46.36.174','23.97.201.12']);
ko.applyBindings(komodel);
https://codepen.io/lyellick0506/pen/NGJgry
Both the onerror- and onload-callback use "responded" as the message, so there is no way to differentiate between them:
this.img.onerror = function (e) {
if (_that.inUse) {
_that.inUse = false;
_that.callback('responded', e); // <--- change this to a different message
}
};
Alternatively you could just check if the e parameter has been set:
new ping(s.name, function (status, e) {
s.status(e ? "error" : status);
});

Promise returning unexpectedly

I have the following:
return indexedDbClient.getStorageUsedInGb().then(function (storageUsedInGb) {
var evictedMediaGuids = [];
storageUsedInGb = parseFloat(storageUsedInGb);
if (storageUsedInGb > storageQuotaInGb) {
return new Promise(function(resolve, reject){
const store = database.transaction(storeName, "readwrite").objectStore(storeName);
(function loop(storageUsedInGb) {
if (storageUsedInGb <= storageQuotaInGb) {
resolve({
evictedMediaGuids: evictedMediaGuids,
shouldStopStoring: false
});
} else {
const latestMediaRequest = store.getAll();
latestMediaRequest.onsuccess = function (event) {
const allData = event.target.result;
const targetEntry = allData[0];
const deleteRequest = store.delete(targetEntry.media.guid);
evictedMediaGuids.push(targetEntry.media.guid);
deleteRequest.onsuccess = loop.bind(null, storageUsedInGb - event.target.media.size / 1024 / 1000 / 1000);
deleteRequest.onerror = reject;
}
latestMediaRequest.onerror = reject;
}
})(storageUsedInGb); // call immediately
})
} else {
return Promise.resolve({
evictedMediaGuids: evictedMediaGuids,
shouldStopStoring: false
});
}
}).then(function (storeObject) {
// do stuff to object
return Promise.resolve(storeObject)
});
The idea is that loop(storageUsedInGb) forces the resolution to wait for the return; however handleStoreObject gets invoked immediately after loop - with no sign of the latestMediaRequest onsuccess handler being invoked. What am I doing wrong?
I am using bluebird in case it matters.

Deferred then of then is undefined for IndexedDb

I'm using CanJs and I'm learning jquery deferred but I have a problem.
I created a controller as sort of Singleton to manage data in IndexedDb.
First of all, I created an openDb function like this:
openDbDeferred: null,
openDb: function (dbName, dbVersion) {
console.log('Open DB...');
var openDbDeferred = this.openDbDeferred;
if (!openDbDeferred || openDbDeferred.isRejected()) {
openDbDeferred = $.Deferred();
var db;
var req = indexedDB.open(dbName, dbVersion);
req.onsuccess = function (evt) {
db = this.result;
console.log('openDB SUCCESS');
openDbDeferred.resolve(db);
};
req.onerror = function (evt) {
console.error("[ERROR] openDb: " + evt);
openDbDeferred.reject();
};
req.onupgradeneeded = function (evt) {
console.log('openDb.onupgradeneeded');
var db = evt.target.result;
var store = db.createObjectStore('sessioni', {keyPath: 'idSession'});
store.createIndex('by_url', 'url', {unique: false});
store.createIndex('by_startDate', 'startDate', {unique: false});
store.createIndex('by_endDate', 'endDate', {unique: false});
};
}
return openDbDeferred.promise();
}
Then I created a function to retrieve all data in DB:
getFilesList: function () {
var getDataDeferred;
return this.openDb('session-db', 1).then(function (db) {
console.log('Find all records...');
getDataDeferred = $.Deferred();
var tx = db.transaction("sessioni", "readwrite");
var store = tx.objectStore("sessioni");
var items = [];
tx.oncomplete = function() {
//console.log(items);
getDataDeferred.resolve(items);
console.log('Transazione getFilesList completata');
};
tx.onfailure = function(evt) {
getDataDeferred.reject();
console.error('[ERROR] Transazione getFilesList fallita: ' + evt);
};
var cursorRequest = store.openCursor();
cursorRequest.onsuccess = function (evt) {
var cursor = evt.target.result;
if (cursor) {
items.push(cursor.value);
cursor.continue();
}
};
cursorRequest.onerror = function (error) {
console.error('findAll [ERROR]: ' + error);
};
});
return getDataDeferred.promise();
}
I declared this controller in another controller to call getFilesList function:
retreiveAllData: function() {
return this.sessionManageModel.getFilesList().than(function(items) {
console.log(items)
return items;
});
}
When the retreiveAllData function is called, it returns 'undefined' because items is 'undefined'.
How can I obtain items in retreiveAllData function?
You've got two return statements in your getFilesList function. The second one should actually be inside the then callback - which currently returns undefined as you observe.
getFilesList: function () {
// no need to declare deferred variable outside of the callback
return this.openDb('session-db', 1).then(function (db) {
var getDataDeferred = $.Deferred();
… // do all the stuff
return getDataDeferred; // place the `return` here
});
// not here!
}

Javascript/HTML5 trying to create a BinaryStreamReader

I'm trying to create a simple binaryStreamReader in javascript. Currently I have the following:
function BinaryStreamReader(file){
var reader = new FileReader();
this.readBytes = function(start, bytes){
reader.readAsBinaryString(file.slice(start,bytes));
reader.onload = function(e){
return e.target.result; //<-- this doesn't work now :P
}
reader.onerror = function(e){
alert("FileError: " + e.target.error.code);
}
}
}
However, I want to use it like this
var bsr = new BinaryStreamReader();
var data = bsr.readBytes(0,128);
Obviously, readBytes() isn't returning anything in my class. Is it possible to let it return whatever onLoad returns?
function BinaryStreamReader(file){
var reader = new FileReader();
this.callback = null;
this.ready = function(callback) {
if(callback) {
this.callback = callback;
}
}
this.readBytes = function(start, bytes){
reader.readAsBinaryString(file.slice(start,bytes));
reader.onload = function(e){
if(this.callback !== null) {
this.callback(e.target.result);
}
}
reader.onerror = function(e){
alert("FileError: " + e.target.error.code);
}
return this;
}
}
var bsr = new BinaryStreamReader();
var data = bsr.readBytes(0,128).ready(function(data) {
alert(data);
});
this should do the trick...

Categories