Search code examples
oracle-databaseplsqljpegoracle19c

PL/SQL Get Dimensions of JPEG Image


Support for Oracle Multimedia was dropped in Oracle 19c, so my code to extract dimensions from a JPEG image is throwing an error. Is there a workaround to this issue?

For Oracle 12, my code looked like this:

    BEGIN
        img := ORDSYS.ORDImage.init('FILE', my_dir, my_img_name);
        img.setProperties();
        w := img.getWidth();
        h := img.getHeight();
    EXCEPTION
        WHEN OTHERS THEN
            w := NULL;
            h := NULL;
    END;

Solution

  • Based on code found in a response to "Getting Image size of JPEG from its binary" (I'm not sure which language), I came up with this procedure:

    PROCEDURE p_jpegstats(directory_in IN  VARCHAR2,
                          filename_in  IN  VARCHAR2,
                          height_out   OUT INTEGER,
                          width_out    OUT INTEGER,
                          bpc_out      OUT INTEGER,  -- bits per channel
                          cps_out      OUT INTEGER   -- colors per component
                         ) IS
        file    bfile;
        pos     INTEGER:=1;
        h       VARCHAR2(4);
        w       VARCHAR2(4);
        mrkr    VARCHAR2(2);
        len     VARCHAR2(4);
        bpc     VARCHAR2(2);
        cps     VARCHAR2(2);
    
        --  Declare a quick helper procedure for readability
        PROCEDURE next_byte(buf out varchar2, amt INTEGER:=1) IS
            cnt     INTEGER;
        BEGIN
            cnt := amt;
            dbms_lob.read(file, cnt, pos, buf);
            pos := pos + cnt;
        END next_byte;
    BEGIN
    -- This code is based off of code found here: https://stackoverflow.com/a/48488655/3303651
    
    -- Open the file
        file := bfilename(directory_in, filename_in);
        dbms_lob.fileopen(file);
    
    -- Init the output variables in case something goes awry.
        height_out := NULL;
        width_out  := NULL;
        bpc_out    := NULL;
        cps_out    := NULL;
    
        LOOP
        BEGIN
            LOOP
                next_byte(mrkr);
                EXIT WHEN mrkr <> 'FF';
            END LOOP;
            CONTINUE WHEN mrkr = 'D8';  -- Start of image (SOI)
            EXIT WHEN mrkr = 'D9';      -- End of image (EOI)
            CONTINUE WHEN mrkr BETWEEN 'D0' AND 'D7';
            CONTINUE WHEN mrkr = '01';  -- TEM
            next_byte(len, 2);
            IF mrkr = 'C0' THEN
                next_byte(bpc);    -- bits per channel
                next_byte(h, 2);   -- height
                next_byte(w, 2);   -- width
                next_byte(cps);    -- colors per component
                EXIT;
            END IF;
            pos := pos + to_number(len, 'XXXX') - 2;
        EXCEPTION WHEN OTHERS THEN EXIT; END;
        END LOOP;
    
    -- Write back the values we found
        height_out := to_number(h, 'XXXX');
        width_out  := to_number(w, 'XXXX');
        bpc_out    := to_number(bpc, 'XX');
        cps_out    := to_number(cps, 'XX');
    
    -- close out the file
        dbms_lob.fileclose(file);
    END p_jpegstats;
    

    This will throw an error if the directory is invalid or the file can't be opened. If the outputs are NULL, then there was some other issue.

    It's probably not the most efficient or elegant code (I'm not a pro with PL/SQL [yet!]), but it works. Here is an example usage:

    DECLARE
        h       INTEGER;
        w       INTEGER;
        bpc     INTEGER;
        cps     INTEGER;
    BEGIN
        p_jpegstats('MY_DIR', 'my_image.jpg', h, w, bpc, cps);
        DBMS_OUTPUT.PUT_LINE(w || ' x ' || h || '  ' || bpc || '  ' || cps);
    END;
    /
    

    This ought to return something like

    800 x 200  8  3
    

    Edit: Removed unused variable.