Search code examples
javascriptzlibreactjs.net

How to decompress image in JavaScript that was compressed by MS Sql


I have an SQL table with two Columns:

[filedata] [varbinary](max) NULL
[Compressed File Data] [varbinary](max) NULL

The [Compressed File Data] is populated with with COMPRESS([filedata]). I store an image byte array in [filedata].

I have a react component:

import React from 'react';
import Config from 'config';
import { Gunzip, decompress } from 'zlib'
import "./Item.css";

class ItemList extends React.Component {
    constructor() {
        super();
        this.state = {
            name: 'React',
            apiData: [],
        };
    }

    async componentDidMount() {
        console.log('app mounted');
        /*global fetch */
        const tokenString = sessionStorage.getItem("token");
        const token = JSON.parse(tokenString);
        const encodedValue = encodeURIComponent(token.customerNumber);
        const response = await fetch(Config.apiUrl + `/api/Items?customerNumber=${encodedValue}`, {
            method: "GET",
            headers: {
                'Content-Type': "application/json",
                'Authorization': 'Bearer ' + token.token
            }
        });
        
        const json = await response.json();
        console.log(json);
        this.setState({ itemList: json.$values });        
    }

    //DecompressImage(compressedImage) {
    //    const buffer = Buffer.from(`${compressedImage}`, 'base64');
    //    Gunzip(buffer, (err, buffer) => {
    //        if (err) {
    //            console.error('An error occurred:', err);                
    //        }
    //        console.log(buffer.toString());
    //        return buffer;
    //    });
    //}

    DecompressImage(compressedImage) {
        console.log(compressedImage);
        var response = new Uint8Array(compressedImage);
        var compressed = response.subarray(0, response.byteLength - 4);
        var gunzip = new Gunzip(compressed);
        var decompress = gunzip.decompress();
        console.log(decompress)
        return decompress;
    }


    render() {
        const items = this.state.itemList;

        return (
            <table className="table table-striped table-bordered">
                <thead>
                    <tr>
                        <th>Item Number</th>
                        <th>Item Description</th>
                        <th>Item Image</th>
                    </tr>
                </thead>
                <tbody>
                    {items && items.map((item, index) =>
                        <tr key={index}>
                            <td>{item.itemNumber}</td>
                            <td>{item.itemDescription}</td>
                            <td><img className="fit-picture" src={"data:image/png;base64," + this.DecompressImage(item.imageData)} id={item.itemNumber + "Img"} alt={item.itemNumber} /></td>
                        </tr>
                    )}
                </tbody>
            </table>
        );
    }
}
export default ItemList;

For the life of me I cannot get DecompressImage(compressedImage) working. This worked before I tried to use compressed data. The commented DecompressImage(compressedImage) returns nothing and no log entry. The second one has an error on decompress that decompress is undefigned.

I have tried code from: nodejs or this stackoverflow question.

Any help would be appreciated.

nodejs version 14.17.1


Solution

  • First, make sure you're getting a Base64 string as input to DecompressImage(). The problem might be that you're getting a hex string when you want an ArrayBuffer or a Base64 string. A hex string over the wire is even more inefficient than the uncompressed data. If you're sure that you're actually using a Base64 string from the JSON, you convert to a Uint8Array and decompress it.

    Now that you've ensured you have a base64 input, note that the NPM zlib library is outdated and yields very low performance compared to modern alternatives like pako or fflate (my own library). As fflate is both smaller and faster than pako, I'll use it for this answer. Here's how you might go about decompressing the data:

    // Install both fflate and base64-js for maximum speed
    import * as b64 from 'base64-js';
    import { gunzipSync } from 'fflate';
    // This should replace DecompressImage
    // Decompresses gzip + base64 to base64
    function decompressToBase64(base64String) {
      const bytes = b64.toByteArray(base64String);
      const decompressed = gunzipSync(bytes);
      return b64.fromByteArray(decompressed);
    }