Search Unity

  1. Click here to see what's on sale for the "Best of Super Sale" on the Asset Store
    Dismiss Notice
  2. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

Convert PNG to JPG without writing to disk

Discussion in 'Scripting' started by Brad-Keys, Oct 7, 2009.

  1. Brad-Keys

    Brad-Keys

    Joined:
    May 1, 2006
    Posts:
    161
    Goal
    I'm creating a feature that takes a screen capture of the Unity game and uploads it to my web server using the web player. I've got this working just fine.

    Issue
    The problem is that the files being uploaded are unnecessarily large (300KB+). I could do some image manipulation in PHP, but I'd like to stay away from having my server doing all that work. .Net has some functionality to convert images to other formats, but as far as I've seen those methods require you to write the newly converted image to disk... which I can't have happen since I'm wanting to do this in the web player.

    Solution Needed
    I need Unity to convert the PNG image to JPG format without writing to disk.

    Any ideas on how I can achieve this?
     
    Meltdown likes this.
  2. SoarionStudios

    SoarionStudios

    Joined:
    Apr 23, 2009
    Posts:
    49
    While technically, this is not really "the answer", I have "an answer" :)

    If you look around on this forum thread: https://mollyrocket.com/forums/viewtopic.php?p=5898 then you can find code for a public domain JPG library as well as pointers to some PNG decompression source code. You could likely cobble those together to do what you need if you can port all that to C#... Or if you have the Advanced version, you could do it in native Obj-C or C++ and do it that way?

    :)

    Stephen
     
  3. MatthewW

    MatthewW

    Joined:
    Nov 30, 2006
    Posts:
    1,355
    This should be fairly easy with .NET, but it looks like Unity's Mono environment is lacking the proper functionality.

    This line:

    Code (csharp):
    1.  
    2. var codecs:ImageCodecInfo[] = ImageCodecInfo.GetImageEncoders();
    3.  
    Produces:

    Code (csharp):
    1.  
    2. DllNotFoundException: gdiplus.dll
    3. System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders ()
    4. System.Drawing.GDIPlus..cctor ()
    5. UnityEditor.EditorGUIUtility:RenderGameViewCameras(Rect, Rect, Boolean, Boolean)
    6. UnityEditor.EditorGUIUtility:RenderGameViewCameras(Rect, Rect, Boolean, Boolean)
    7. UnityEditor.GameView:OnGUI()
    8. System.Reflection.MonoMethod:InternalInvoke(Object, Object[])
    9. System.Reflection.MonoMethod:InternalInvoke(Object, Object[])
    10. System.Reflection.MonoMethod:Invoke(Object, BindingFlags, Binder, Object[], CultureInfo)
    11. System.Reflection.MethodBase:Invoke(Object, Object[])
    12. UnityEditor.HostView:Invoke(String)
    13. UnityEditor.DockArea:OnGUI()
    I suppose you could find a totally exposed C# implementation of JPEG compression, but with functionality already present in .NET I doubt there's one out there...
     
  4. Brad-Keys

    Brad-Keys

    Joined:
    May 1, 2006
    Posts:
    161
    Ya, its turning out to be really inconvenient. Would it be worth me asking for a feature request? Or is this too obscure of a problem?

    I doubt that too. I'll keep looking for a bit, but I'm afraid that for the time being I'll just have to handle the compression with PHP.
     
  5. MatthewW

    MatthewW

    Joined:
    Nov 30, 2006
    Posts:
    1,355
    It's worth requesting, at least. It might be something simple for Unity to add, although my guess is it's a deliberate feature reduction in order to save file size on the web player...
     
  6. MatthewW

    MatthewW

    Joined:
    Nov 30, 2006
    Posts:
    1,355
    I ported some AS3 JPEG encoding code to Unity JavaScript--you can now do this natively: http://blur.st/jpeg
     
  7. Brad-Keys

    Brad-Keys

    Joined:
    May 1, 2006
    Posts:
    161
    Thanks, this will be extremely useful.
     
  8. Brad-Keys

    Brad-Keys

    Joined:
    May 1, 2006
    Posts:
    161
    I tried it out but I'm having some troubles. I'm trying to upload the image to my website, but the file type that's getting uploaded is returning as: application/octet-stream rather than image/jpeg. Here is my modified version of your example project.

    Code (csharp):
    1.  
    2. function ScreenshotEncode()
    3. {
    4.     // wait for graphics to render
    5.     yield WaitForEndOfFrame();
    6.    
    7.     // create a texture to pass to encoding
    8.     var texture:Texture2D = new Texture2D (Screen.width, Screen.height, TextureFormat.RGB24, false);
    9.    
    10.     // put buffer into texture
    11.     texture.ReadPixels(Rect(0.0, 0.0, Screen.width, Screen.height), 0.0, 0.0);
    12.     texture.Apply();
    13.  
    14.     // split the process up--ReadPixels() and the GetPixels() call inside of the encoder are both pretty heavy
    15.     yield;
    16.    
    17.     // create our encoder for this texture
    18.     var encoder:JPGEncoder = new JPGEncoder(texture, 75.0);
    19.    
    20.     // encoder is threaded; wait for it to finish
    21.     while(!encoder.isDone)
    22.         yield;
    23.        
    24.     // Create a Web Form
    25.     var form = new WWWForm();
    26.     form.AddBinaryData("avatar", encoder.GetBytes());
    27.  
    28.     // Upload to PHP script    
    29.     var w = WWW(screenShotURL, form);
    30.     yield w;
    31.    
    32.     if (w.error != null) {
    33.         print(w.error);    
    34.     }
    35.     else {
    36.         print(w.data);    
    37.     }
    38. }
     
  9. MatthewW

    MatthewW

    Joined:
    Nov 30, 2006
    Posts:
    1,355
    This looks like a Unity thing:

    Code (csharp):
    1.  
    2. If mimeType is not given and first 8 bytes of the data match PNG format header, then the data is sent with "image/png" mimetype. Otherwise it is sent with "application/octet-stream" mimetype.
    3.  
    The MIME type shouldn't matter too much, depending on what your server-side code looks like. Are you just saving to disk?
     
  10. MatthewW

    MatthewW

    Joined:
    Nov 30, 2006
    Posts:
    1,355
    Actually, based on the docs, just try:
    Code (csharp):
    1.  
    2. form.AddBinaryData("avatar", encoder.GetBytes(), "screenshot.jpg", "image/jpeg");
    3.  
     
  11. Brad-Keys

    Brad-Keys

    Joined:
    May 1, 2006
    Posts:
    161
    Ah, yes that did the trick. Works beautifully.

    Thanks a lot.
     
  12. gamma.psh

    gamma.psh

    Joined:
    Jan 25, 2011
    Posts:
    44
    I've a mobile project where i should use a jpg encoder to lower traffic, when uploading/downloading images from the device.

    Now I wanted to translate the js version of the script above to C#, but I fail with the bitoperations.
    Maybe someone has a solution.

    Code (csharp):
    1. // IO functions
    2.     private int bytenew = 0;
    3.     private int bytepos = 7;
    4.     private ByteArray byteout = new ByteArray();
    5.  
    6.  
    7. private void writeBits(BitString bs)
    8. {
    9.     int value = bs.val;
    10.     int posval = bs.len-1;
    11.     while ( posval >= 0 ) {
    12.         if (value  System.Convert.ToUInt32(1 << posval)) {
    13.             bytenew |= System.Convert.ToUInt32(1 << bytepos);
    14.         }
    15.         posval--;
    16.         bytepos--;
    17.         if (bytepos < 0) {
    18.             if (bytenew == 0xFF) {
    19.                 writeByte(0xFF);
    20.                 writeByte(0);
    21.             }
    22.             else {
    23.                 writeByte(bytenew);
    24.             }
    25.             bytepos=7;
    26.             bytenew=0;
    27.         }
    28.     }
    29. }
    The first If statement trows an error that it cannot convert lont to bool, I see the problem that: value System.Convert.ToUInt32(1 << posval) is not a bool, but it is used like one, but I fail to get a sollution.

    The Second error is one line below bytenew |= System.Convert.ToUInt32(1 << bytepos);
    where bytenew is an int, and System.Convert.ToUInt32() is obviously an uint.

    A Similar Problem are several Operations that look like the one below:
    Code (csharp):
    1. private void writeWord(int value)
    2. {
    3.     writeByte((value>>8)0xFF);
    4.     writeByte((value   )0xFF);
    5. }
    writeByte(value:byte) expects byte, and obviously throws an error.

    where an int is used as byte, I suspect that I just need to convert the int values to bytes, and use them as bytes, but how is this done?

    I have the impression that i need to use something like a bit converter, but thats some levels above my understanding :(
    So is my assumption to use a BitConverter right?
    And if I'm right, what Im really not sure about, what is the mono thing for System.BitConverter? does it also exist or what do I have to use there?

    Maybe someone whats to see the whole script, for this I attatched a Zip with the UnityProject from Matthew above, and made a Second JPGEncoder script in C# with its own namespace, to avoid Class name Problems.

    Whats done until now is that I changed all variable and method declarations to C# syntax, and set their private/public state, if it wasn't done in js.

    View attachment $JPEG Encoding.zip
     
    Last edited: Jun 17, 2011
  13. gamma.psh

    gamma.psh

    Joined:
    Jan 25, 2011
    Posts:
    44
    It works now, but I have no idea how "save" my changes are :/

    The expression (value System.Convert.ToUInt32(1 << posval)) gets checked if 0, instead as boolean value, like in js
    and all the other type errors are solved by just casting to (int) or (byte) that should be more or less the same that js is doing, but js is doing this without being explicitly told to.

    Maybe someone wants to have a look @, or maybe it's useful for someone looking for this in C#

    Code (csharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using System.IO;
    5. using System.Threading;
    6.  
    7. /*
    8.     * ---
    9.     * Translation to C# for Unity by GammaPsh
    10.     *
    11.     * prior Version is from Matthew (Unity Forum), found here:
    12.     * http://blur.st/jpeg
    13.     *
    14.     * Original copyright notice is below:
    15.     * ---
    16.     *
    17.     * Ported to UnityScript by Matthew Wegner, Flashbang Studios
    18.     *
    19.     * Original code is from as3corelib, found here:
    20.     * http://code.google.com/p/as3corelib/source/browse/trunk/src/com/adobe/images/JPGEncoder.as
    21.     *
    22.     * Original copyright notice is below:
    23.     */
    24.    
    25. /*
    26.   Copyright (c) 2008, Adobe Systems Incorporated
    27.   All rights reserved.
    28.  
    29.   Redistribution and use in source and binary forms, with or without
    30.   modification, are permitted provided that the following conditions are
    31.   met:
    32.  
    33.   * Redistributions of source code must retain the above copyright notice,
    34.     this list of conditions and the following disclaimer.
    35.  
    36.   * Redistributions in binary form must reproduce the above copyright
    37.     notice, this list of conditions and the following disclaimer in the
    38.     documentation and/or other materials provided with the distribution.
    39.  
    40.   * Neither the name of Adobe Systems Incorporated nor the names of its
    41.     contributors may be used to endorse or promote products derived from
    42.     this software without specific prior written permission.
    43.  
    44.   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
    45.   IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
    46.   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    47.   PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    48.   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    49.   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    50.   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    51.   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    52.   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    53.   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    54.   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    55. */
    56.  
    57.  
    58. class ByteArray
    59. {
    60.     private MemoryStream stream;
    61.     private BinaryWriter writer;
    62.    
    63.     public ByteArray()
    64.     {
    65.         stream = new MemoryStream();
    66.         writer = new BinaryWriter(stream);
    67.     }
    68.    
    69.     /**
    70.     * Function from AS3--add a byte to our stream
    71.     */
    72.     public void writeByte(byte value)
    73.     {
    74.         writer.Write(value);
    75.     }
    76.    
    77. //      public void writeByte(int value)
    78. //      {
    79. //          writer.Write(value);
    80. //      }
    81.    
    82.     /**
    83.     * Spit back all bytes--to either pass via WWW or save to disk
    84.     */
    85.     public byte[] GetAllBytes()
    86.     {
    87.         byte[] buffer = new byte[stream.Length];
    88.         stream.Position = 0;
    89.         stream.Read(buffer, 0, buffer.Length);
    90.        
    91.         return buffer;
    92.     }
    93. }
    94.  
    95. /**
    96. * This should really be a struct--if you care, declare it in C#
    97. */
    98. struct BitString {
    99.    
    100.     private int _lenVal;
    101.     private int _valVal;
    102.  
    103.     public int len
    104.     {
    105.         get
    106.         {
    107.             return _lenVal;
    108.         }
    109.         set
    110.         {
    111.             _lenVal = value;
    112.         }
    113.     }
    114.     public int val
    115.     {
    116.         get
    117.         {
    118.             return _valVal;
    119.         }
    120.         set
    121.         {
    122.             _valVal = value;
    123.         }
    124.     }
    125. }
    126.  
    127. /**
    128. * Another flash class--emulating the stuff the encoder uses
    129. */
    130. class BitmapData
    131. {
    132.     public int height;
    133.     public int width;
    134.    
    135.     private Color[] pixels;
    136.    
    137.     /**
    138.     * Pull all of our pixels off the texture (Unity stuff isn't thread safe, and this is faster)
    139.     */
    140.     public BitmapData(Texture2D texture)
    141.     {
    142.         this.height = texture.height;
    143.         this.width = texture.width;
    144.        
    145.         pixels = texture.GetPixels();
    146.     }
    147.  
    148.     /**
    149.     * Mimic the flash function
    150.     */
    151.     public Color getPixelColor(int x, int y)
    152.     {  
    153.         if(x >= width)
    154.             x = width - 1;
    155.        
    156.         if(y >= height)
    157.             y = height - 1;
    158.            
    159.         if(x < 0)
    160.             x = 0;
    161.            
    162.         if(y < 0)
    163.             y = 0;
    164.        
    165.         return pixels[y * width + x];
    166.     }
    167. }
    168.  
    169. /**
    170.  * Class that converts BitmapData into a valid JPEG
    171.  */    
    172. public class JPGEncoder
    173. {
    174.  
    175.     // Static table initialization
    176.  
    177.     private int[] ZigZag = {
    178.          0, 1, 5, 6,14,15,27,28,
    179.          2, 4, 7,13,16,26,29,42,
    180.          3, 8,12,17,25,30,41,43,
    181.          9,11,18,24,31,40,44,53,
    182.         10,19,23,32,39,45,52,54,
    183.         20,22,33,38,46,51,55,60,
    184.         21,34,37,47,50,56,59,61,
    185.         35,36,48,49,57,58,62,63
    186.     };
    187.  
    188.     private int[] YTable = new int[64];
    189.     private int[] UVTable = new int[64];
    190.     private float[] fdtbl_Y = new float[64];
    191.     private float[] fdtbl_UV = new float[64];
    192.  
    193.     private void initQuantTables(int sf)
    194.     {
    195.         int i;
    196.         float t;
    197.         int[] YQT = {
    198.             16, 11, 10, 16, 24, 40, 51, 61,
    199.             12, 12, 14, 19, 26, 58, 60, 55,
    200.             14, 13, 16, 24, 40, 57, 69, 56,
    201.             14, 17, 22, 29, 51, 87, 80, 62,
    202.             18, 22, 37, 56, 68,109,103, 77,
    203.             24, 35, 55, 64, 81,104,113, 92,
    204.             49, 64, 78, 87,103,121,120,101,
    205.             72, 92, 95, 98,112,100,103, 99
    206.         };
    207.         for (i = 0; i < 64; i++) {
    208.             t = Mathf.Floor((YQT[i]*sf+50)/100);
    209.             if (t < 1) {
    210.                 t = 1;
    211.             } else if (t > 255) {
    212.                 t = 255;
    213.             }
    214.             YTable[ZigZag[i]] = Mathf.FloorToInt(t);
    215.         }
    216.         int[] UVQT = {
    217.             17, 18, 24, 47, 99, 99, 99, 99,
    218.             18, 21, 26, 66, 99, 99, 99, 99,
    219.             24, 26, 56, 99, 99, 99, 99, 99,
    220.             47, 66, 99, 99, 99, 99, 99, 99,
    221.             99, 99, 99, 99, 99, 99, 99, 99,
    222.             99, 99, 99, 99, 99, 99, 99, 99,
    223.             99, 99, 99, 99, 99, 99, 99, 99,
    224.             99, 99, 99, 99, 99, 99, 99, 99
    225.         };
    226.         for (i = 0; i < 64; i++) {
    227.             t = Mathf.Floor((UVQT[i]*sf+50)/100);
    228.             if (t < 1) {
    229.                 t = 1;
    230.             } else if (t > 255) {
    231.                 t = 255;
    232.             }
    233.             UVTable[ZigZag[i]] = Mathf.FloorToInt(t);
    234.         }
    235.         float[] aasf = {
    236.             1.0f, 1.387039845f, 1.306562965f, 1.175875602f,
    237.             1.0f, 0.785694958f, 0.541196100f, 0.275899379f
    238.         };
    239.         i = 0;
    240.         for (int row = 0; row < 8; row++)
    241.         {
    242.             for (int col = 0; col < 8; col++)
    243.             {
    244.                 fdtbl_Y[i]  = (1.0f / (YTable [ZigZag[i]] * aasf[row] * aasf[col] * 8.0f));
    245.                 fdtbl_UV[i] = (1.0f / (UVTable[ZigZag[i]] * aasf[row] * aasf[col] * 8.0f));
    246.                 i++;
    247.             }
    248.         }
    249.     }
    250.  
    251.     private BitString[] YDC_HT;
    252.     private BitString[] UVDC_HT;
    253.     private BitString[] YAC_HT;
    254.     private BitString[] UVAC_HT;
    255.  
    256.     private BitString[] computeHuffmanTbl(int[] nrcodes, int[] std_table)
    257.     {
    258.         int codevalue = 0;
    259.         int pos_in_table = 0;
    260.         BitString[] HT = new BitString[16 * 16];
    261.         for (int k = 1; k<=16; k++) {
    262.             for (int j=1; j<=nrcodes[k]; j++) {
    263.                 HT[std_table[pos_in_table]] = new BitString();
    264.                 HT[std_table[pos_in_table]].val = codevalue;
    265.                 HT[std_table[pos_in_table]].len = k;
    266.                 pos_in_table++;
    267.                 codevalue++;
    268.             }
    269.             codevalue*=2;
    270.         }
    271.         return HT;
    272.     }
    273.  
    274.     private int[] std_dc_luminance_nrcodes = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0};
    275.     private int[] std_dc_luminance_values = {0,1,2,3,4,5,6,7,8,9,10,11};
    276.     private int[] std_ac_luminance_nrcodes = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d};
    277.     private int[] std_ac_luminance_values = {
    278.         0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,
    279.         0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,
    280.         0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,
    281.         0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,
    282.         0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,
    283.         0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,
    284.         0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,
    285.         0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,
    286.         0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,
    287.         0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,
    288.         0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,
    289.         0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,
    290.         0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,
    291.         0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,
    292.         0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,
    293.         0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,
    294.         0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,
    295.         0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,
    296.         0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
    297.         0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
    298.         0xf9,0xfa
    299.     };
    300.  
    301.     private int[] std_dc_chrominance_nrcodes = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0};
    302.     private int[] std_dc_chrominance_values = {0,1,2,3,4,5,6,7,8,9,10,11};
    303.     private int[] std_ac_chrominance_nrcodes = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77};
    304.     private int[] std_ac_chrominance_values = {
    305.         0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,
    306.         0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,
    307.         0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,
    308.         0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,
    309.         0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,
    310.         0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,
    311.         0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,
    312.         0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,
    313.         0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,
    314.         0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,
    315.         0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,
    316.         0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,
    317.         0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,
    318.         0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,
    319.         0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,
    320.         0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,
    321.         0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,
    322.         0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,
    323.         0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,
    324.         0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,
    325.         0xf9,0xfa
    326.     };
    327.  
    328.     private void initHuffmanTbl()
    329.     {
    330.         YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);
    331.         UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);
    332.         YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);
    333.         UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);
    334.     }
    335.  
    336.     private BitString[] bitcode = new BitString[65535];
    337.     private int[] category = new int[65535];
    338.  
    339.     private void initCategoryfloat()
    340.     {
    341.         int nrlower = 1;
    342.         int nrupper = 2;
    343.         int nr;
    344.         BitString bs;
    345.         for (int cat=1; cat<=15; cat++) {
    346.             //Positive numbers
    347.             for (nr=nrlower; nr<nrupper; nr++) {
    348.                 category[32767+nr] = cat;
    349.  
    350.                 bs = new BitString();
    351.                 bs.len = cat;
    352.                 bs.val = nr;
    353.                 bitcode[32767+nr] = bs;
    354.             }
    355.             //Negative numbers
    356.             for (nr=-(nrupper-1); nr<=-nrlower; nr++) {
    357.                 category[32767+nr] = cat;
    358.  
    359.                 bs = new BitString();
    360.                 bs.len = cat;
    361.                 bs.val = nrupper-1+nr;
    362.                 bitcode[32767+nr] = bs;
    363.             }
    364.             nrlower <<= 1;
    365.             nrupper <<= 1;
    366.         }
    367.     }
    368.  
    369.     // IO functions
    370.     private int bytenew = 0;
    371.     private int bytepos = 7;
    372.     private ByteArray byteout = new ByteArray();
    373.    
    374.     /**
    375.     * Get the result
    376.     */
    377.     public byte[] GetBytes()
    378.     {
    379.         if(!isDone)
    380.         {
    381.             Debug.LogError("JPEGEncoder not complete, cannot get bytes!");
    382.             return new byte[0];
    383.         }
    384.        
    385.         return byteout.GetAllBytes();
    386.     }
    387.    
    388.     private void writeBits(BitString bs)
    389.     {
    390.         int value = bs.val;
    391.         int posval = bs.len-1;
    392.         while ( posval >= 0 ) {
    393.             if ((value  System.Convert.ToUInt32(1 << posval)) != 0) {
    394.                 bytenew |= (int)System.Convert.ToUInt32(1 << bytepos);
    395.             }
    396.             posval--;
    397.             bytepos--;
    398.             if (bytepos < 0) {
    399.                 if (bytenew == 0xFF) {
    400.                     writeByte(0xFF);
    401.                     writeByte(0);
    402.                 }
    403.                 else {
    404.                     writeByte((byte)bytenew);
    405.                 }
    406.                 bytepos=7;
    407.                 bytenew=0;
    408.             }
    409.         }
    410.     }
    411.  
    412.     private void writeByte(byte value)
    413.     {
    414.         byteout.writeByte(value);
    415.     }
    416.    
    417. //      private void writeByte(int value)
    418. //      {
    419. //          byteout.writeByte(value);
    420. //      }
    421.  
    422.     private void writeWord(int value)
    423.     {
    424.         writeByte((byte)((value>>8)0xFF));
    425.         writeByte((byte)((value)0xFF));
    426.     }
    427.  
    428.     // DCT  quantization core
    429.  
    430.     private float[] fDCTQuant(float[] data,float[] fdtbl)
    431.     {
    432.         float tmp0;
    433.         float tmp1;
    434.         float tmp2;
    435.         float tmp3;
    436.         float tmp4;
    437.         float tmp5;
    438.         float tmp6;
    439.         float tmp7;
    440.         float tmp10;
    441.         float tmp11;
    442.         float tmp12;
    443.         float tmp13;
    444.         float z1;
    445.         float z2;
    446.         float z3;
    447.         float z4;
    448.         float z5;
    449.         float z11;
    450.         float z13;
    451.         int i;
    452.         /* Pass 1: process rows. */
    453.         int dataOff=0;
    454.         for (i=0; i<8; i++) {
    455.             tmp0 = data[dataOff+0] + data[dataOff+7];
    456.             tmp7 = data[dataOff+0] - data[dataOff+7];
    457.             tmp1 = data[dataOff+1] + data[dataOff+6];
    458.             tmp6 = data[dataOff+1] - data[dataOff+6];
    459.             tmp2 = data[dataOff+2] + data[dataOff+5];
    460.             tmp5 = data[dataOff+2] - data[dataOff+5];
    461.             tmp3 = data[dataOff+3] + data[dataOff+4];
    462.             tmp4 = data[dataOff+3] - data[dataOff+4];
    463.  
    464.             /* Even part */
    465.             tmp10 = tmp0 + tmp3;    /* phase 2 */
    466.             tmp13 = tmp0 - tmp3;
    467.             tmp11 = tmp1 + tmp2;
    468.             tmp12 = tmp1 - tmp2;
    469.  
    470.             data[dataOff+0] = tmp10 + tmp11; /* phase 3 */
    471.             data[dataOff+4] = tmp10 - tmp11;
    472.  
    473.             z1 = (tmp12 + tmp13) * 0.707106781f; /* c4 */
    474.             data[dataOff+2] = tmp13 + z1; /* phase 5 */
    475.             data[dataOff+6] = tmp13 - z1;
    476.  
    477.             /* Odd part */
    478.             tmp10 = tmp4 + tmp5; /* phase 2 */
    479.             tmp11 = tmp5 + tmp6;
    480.             tmp12 = tmp6 + tmp7;
    481.  
    482.             /* The rotator is modified from fig 4-8 to avoid extra negations. */
    483.             z5 = (tmp10 - tmp12) * 0.382683433f; /* c6 */
    484.             z2 = 0.541196100f * tmp10 + z5; /* c2-c6 */
    485.             z4 = 1.306562965f * tmp12 + z5; /* c2+c6 */
    486.             z3 = tmp11 * 0.707106781f; /* c4 */
    487.  
    488.             z11 = tmp7 + z3;    /* phase 5 */
    489.             z13 = tmp7 - z3;
    490.  
    491.             data[dataOff+5] = z13 + z2; /* phase 6 */
    492.             data[dataOff+3] = z13 - z2;
    493.             data[dataOff+1] = z11 + z4;
    494.             data[dataOff+7] = z11 - z4;
    495.  
    496.             dataOff += 8; /* advance pointer to next row */
    497.         }
    498.  
    499.         /* Pass 2: process columns. */
    500.         dataOff = 0;
    501.         for (i=0; i<8; i++) {
    502.             tmp0 = data[dataOff+ 0] + data[dataOff+56];
    503.             tmp7 = data[dataOff+ 0] - data[dataOff+56];
    504.             tmp1 = data[dataOff+ 8] + data[dataOff+48];
    505.             tmp6 = data[dataOff+ 8] - data[dataOff+48];
    506.             tmp2 = data[dataOff+16] + data[dataOff+40];
    507.             tmp5 = data[dataOff+16] - data[dataOff+40];
    508.             tmp3 = data[dataOff+24] + data[dataOff+32];
    509.             tmp4 = data[dataOff+24] - data[dataOff+32];
    510.  
    511.             /* Even part */
    512.             tmp10 = tmp0 + tmp3;    /* phase 2 */
    513.             tmp13 = tmp0 - tmp3;
    514.             tmp11 = tmp1 + tmp2;
    515.             tmp12 = tmp1 - tmp2;
    516.  
    517.             data[dataOff+ 0] = tmp10 + tmp11; /* phase 3 */
    518.             data[dataOff+32] = tmp10 - tmp11;
    519.  
    520.             z1 = (tmp12 + tmp13) * 0.707106781f; /* c4 */
    521.             data[dataOff+16] = tmp13 + z1; /* phase 5 */
    522.             data[dataOff+48] = tmp13 - z1;
    523.  
    524.             /* Odd part */
    525.             tmp10 = tmp4 + tmp5; /* phase 2 */
    526.             tmp11 = tmp5 + tmp6;
    527.             tmp12 = tmp6 + tmp7;
    528.  
    529.             /* The rotator is modified from fig 4-8 to avoid extra negations. */
    530.             z5 = (tmp10 - tmp12) * 0.382683433f; /* c6 */
    531.             z2 = 0.541196100f * tmp10 + z5; /* c2-c6 */
    532.             z4 = 1.306562965f * tmp12 + z5; /* c2+c6 */
    533.             z3 = tmp11 * 0.707106781f; /* c4 */
    534.  
    535.             z11 = tmp7 + z3;    /* phase 5 */
    536.             z13 = tmp7 - z3;
    537.  
    538.             data[dataOff+40] = z13 + z2; /* phase 6 */
    539.             data[dataOff+24] = z13 - z2;
    540.             data[dataOff+ 8] = z11 + z4;
    541.             data[dataOff+56] = z11 - z4;
    542.  
    543.             dataOff++; /* advance pointer to next column */
    544.         }
    545.  
    546.         // Quantize/descale the coefficients
    547.         for (i=0; i<64; i++) {
    548.             // Apply the quantization and scaling factor  Round to nearest integer
    549.             data[i] = Mathf.Round((data[i]*fdtbl[i]));
    550.         }
    551.         return data;
    552.     }
    553.  
    554.     // Chunk writing
    555.  
    556.     private void writeAPP0()
    557.     {
    558.         writeWord(0xFFE0); // marker
    559.         writeWord(16); // length
    560.         writeByte(0x4A); // J
    561.         writeByte(0x46); // F
    562.         writeByte(0x49); // I
    563.         writeByte(0x46); // F
    564.         writeByte(0); // = "JFIF",'\0'
    565.         writeByte(1); // versionhi
    566.         writeByte(1); // versionlo
    567.         writeByte(0); // xyunits
    568.         writeWord(1); // xdensity
    569.         writeWord(1); // ydensity
    570.         writeByte(0); // thumbnwidth
    571.         writeByte(0); // thumbnheight
    572.     }
    573.  
    574.     private void writeSOF0(int width,int height)
    575.     {
    576.         writeWord(0xFFC0); // marker
    577.         writeWord(17);   // length, truecolor YUV JPG
    578.         writeByte(8);    // precision
    579.         writeWord(height);
    580.         writeWord(width);
    581.         writeByte(3);    // nrofcomponents
    582.         writeByte(1);    // IdY
    583.         writeByte(0x11); // HVY
    584.         writeByte(0);    // QTY
    585.         writeByte(2);    // IdU
    586.         writeByte(0x11); // HVU
    587.         writeByte(1);    // QTU
    588.         writeByte(3);    // IdV
    589.         writeByte(0x11); // HVV
    590.         writeByte(1);    // QTV
    591.     }
    592.  
    593.     private void writeDQT()
    594.     {
    595.         writeWord(0xFFDB); // marker
    596.         writeWord(132);    // length
    597.         writeByte(0);
    598.         int i;
    599.         for (i=0; i<64; i++) {
    600.             writeByte((byte)YTable[i]);
    601.         }
    602.         writeByte(1);
    603.         for (i=0; i<64; i++) {
    604.             writeByte((byte)UVTable[i]);
    605.         }
    606.     }
    607.  
    608.     private void writeDHT()
    609.     {
    610.         writeWord(0xFFC4); // marker
    611.         writeWord(0x01A2); // length
    612.         int i;
    613.  
    614.         writeByte(0); // HTYDCinfo
    615.         for (i=0; i<16; i++) {
    616.             writeByte((byte)std_dc_luminance_nrcodes[i+1]);
    617.         }
    618.         for (i=0; i<=11; i++) {
    619.             writeByte((byte)std_dc_luminance_values[i]);
    620.         }
    621.  
    622.         writeByte(0x10); // HTYACinfo
    623.         for (i=0; i<16; i++) {
    624.             writeByte((byte)std_ac_luminance_nrcodes[i+1]);
    625.         }
    626.         for (i=0; i<=161; i++) {
    627.             writeByte((byte)std_ac_luminance_values[i]);
    628.         }
    629.  
    630.         writeByte(1); // HTUDCinfo
    631.         for (i=0; i<16; i++) {
    632.             writeByte((byte)std_dc_chrominance_nrcodes[i+1]);
    633.         }
    634.         for (i=0; i<=11; i++) {
    635.             writeByte((byte)std_dc_chrominance_values[i]);
    636.         }
    637.  
    638.         writeByte(0x11); // HTUACinfo
    639.         for (i=0; i<16; i++) {
    640.             writeByte((byte)std_ac_chrominance_nrcodes[i+1]);
    641.         }
    642.         for (i=0; i<=161; i++) {
    643.             writeByte((byte)std_ac_chrominance_values[i]);
    644.         }
    645.     }
    646.  
    647.     private void writeSOS()
    648.     {
    649.         writeWord(0xFFDA); // marker
    650.         writeWord(12); // length
    651.         writeByte(3); // nrofcomponents
    652.         writeByte(1); // IdY
    653.         writeByte(0); // HTY
    654.         writeByte(2); // IdU
    655.         writeByte(0x11); // HTU
    656.         writeByte(3); // IdV
    657.         writeByte(0x11); // HTV
    658.         writeByte(0); // Ss
    659.         writeByte(0x3f); // Se
    660.         writeByte(0); // Bf
    661.     }
    662.  
    663.     // Core processing
    664.     private int[] DU = new int[64];
    665.  
    666.     private float processDU(float[] CDU, float[] fdtbl, float DC, BitString[] HTDC, BitString[] HTAC)
    667.     {
    668.         BitString EOB = HTAC[0x00];
    669.         BitString M16zeroes = HTAC[0xF0];
    670.         int i;
    671.  
    672.         float[] DU_DCT = fDCTQuant(CDU, fdtbl);
    673.         //ZigZag reorder
    674.         for (i=0;i<64;i++) {
    675.             DU[ZigZag[i]]=Mathf.RoundToInt(DU_DCT[i]);
    676.         }
    677.         int Diff = Mathf.RoundToInt(DU[0] - DC);
    678.         DC = DU[0];
    679.         //Encode DC
    680.         if (Diff==0) {
    681.             writeBits(HTDC[0]); // Diff might be 0
    682.         } else {
    683.             writeBits(HTDC[category[32767+Diff]]);
    684.             writeBits(bitcode[32767+Diff]);
    685.         }
    686.         //Encode ACs
    687.         int end0pos = 63;
    688.         for (; (end0pos>0)(DU[end0pos]==0); end0pos--) {
    689.         };
    690.         //end0pos = first element in reverse order !=0
    691.         if ( end0pos == 0) {
    692.             writeBits(EOB);
    693.             return DC;
    694.         }
    695.         i = 1;
    696.         while ( i <= end0pos ) {
    697.             int startpos = i;
    698.             for (; (DU[i]==0)  (i<=end0pos); i++) {
    699.             }
    700.             int nrzeroes = i-startpos;
    701.             if ( nrzeroes >= 16 ) {
    702.                 for (int nrmarker=1; nrmarker <= nrzeroes/16; nrmarker++) {
    703.                     writeBits(M16zeroes);
    704.                 }
    705.                 nrzeroes = (nrzeroes0xF);
    706.             }
    707.             writeBits(HTAC[nrzeroes*16+category[32767+DU[i]]]);
    708.             writeBits(bitcode[32767+DU[i]]);
    709.             i++;
    710.         }
    711.         if ( end0pos != 63 ) {
    712.             writeBits(EOB);
    713.         }
    714.         return DC;
    715.     }
    716.  
    717.     private float[] YDU = new float[64];
    718.     private float[] UDU = new float[64];
    719.     private float[] VDU = new float[64];
    720.  
    721.     private void RGB2YUV(BitmapData img,int xpos, int ypos)
    722.     {      
    723.         int pos=0;
    724.         for (int y=0; y<8; y++) {
    725.             for (int x=0; x<8; x++) {
    726.                 Color C = img.getPixelColor(xpos+x,img.height - (ypos+y));
    727.                 float R = C.r * 255;
    728.                 float G = C.g * 255;
    729.                 float B = C.b * 255;
    730.                 YDU[pos]=((( 0.29900f)*R+( 0.58700f)*G+( 0.11400f)*B))-128f;
    731.                 UDU[pos]=(((-0.16874f)*R+(-0.33126f)*G+( 0.50000f)*B));
    732.                 VDU[pos]=((( 0.50000f)*R+(-0.41869f)*G+(-0.08131f)*B));
    733.                 pos++;
    734.             }
    735.         }
    736.     }
    737.  
    738.     /**
    739.      * Constructor for JPEGEncoder class
    740.      *
    741.      * @param quality The quality level between 1 and 100 that detrmines the
    742.      * level of compression used in the generated JPEG
    743.      * @langversion ActionScript 3.0
    744.      * @playerversion Flash 9.0
    745.      * @tiptext
    746.      */
    747.    
    748.     // public flag--other scripts must watch this to know when they can safely get data out
    749.     public bool isDone = false;
    750.     private BitmapData image;
    751.     private int sf = 0;
    752.     public JPGEncoder(Texture2D texture, float quality)
    753.     {  
    754.         // save out texture data to our own data structure
    755.         image = new BitmapData(texture);
    756.        
    757.         if (quality <= 0) {
    758.             quality = 1;
    759.         }
    760.         if (quality > 100.0f) {
    761.             quality = 100.0f;
    762.         }
    763.         if (quality < 50.0f) {
    764.             sf = Mathf.RoundToInt(5000f / quality);
    765.         } else {
    766.             sf = Mathf.RoundToInt(200f - quality*2f);
    767.         }
    768.        
    769.         // spin this off into the background
    770.         var thread = new Thread(doEncoding);
    771.         thread.Start();    
    772.     }
    773.    
    774.     /**
    775.     * Handle our initialization and encoding
    776.     */
    777.     private void doEncoding()
    778.     {
    779.         isDone = false;
    780.    
    781.         Thread.Sleep(5);
    782.        
    783.         // Create tables -- technically we could only do this once for multiple encodes
    784.         initHuffmanTbl();
    785.         initCategoryfloat();
    786.         initQuantTables(sf);
    787.        
    788.         // Do actual encoding
    789.         encode();
    790.        
    791.         // signal that our data is ok to use now
    792.         isDone = true;
    793.        
    794.         // tell the thread to stop--not sure if this is actually needed
    795.         image = null;
    796.         Thread.CurrentThread.Abort();
    797.        
    798.     }
    799.    
    800.     /**
    801.      * Created a JPEG image from the specified BitmapData
    802.      *
    803.      * @param image The BitmapData that will be converted into the JPEG format.
    804.      * @return a ByteArray representing the JPEG encoded image data.
    805.      * @langversion ActionScript 3.0
    806.      * @playerversion Flash 9.0
    807.      * @tiptext
    808.      */
    809.     private void encode()
    810.     {
    811.         // Initialize bit writer
    812.         byteout = new ByteArray();
    813.         bytenew=0;
    814.         bytepos=7;
    815.  
    816.         // Add JPEG headers
    817.         writeWord(0xFFD8); // SOI
    818.         writeAPP0();
    819.         writeDQT();
    820.         writeSOF0(image.width,image.height);
    821.         writeDHT();
    822.         writeSOS();
    823.  
    824.         // Encode 8x8 macroblocks
    825.         float DCY=0;
    826.         float DCU=0;
    827.         float DCV=0;
    828.         bytenew=0;
    829.         bytepos=7;
    830.         for (int ypos=0; ypos<image.height; ypos+=8) {
    831.             for (int xpos=0; xpos<image.width; xpos+=8) {
    832.                 RGB2YUV(image, xpos, ypos);
    833.                 DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
    834.                 DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
    835.                 DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
    836.                
    837.                 // let other threads do stuff too
    838.                 Thread.Sleep(0);
    839.             }
    840.         }
    841.  
    842.         // Do the bit alignment of the EOI marker
    843.         if ( bytepos >= 0 ) {
    844.             BitString fillbits = new BitString();
    845.             fillbits.len = bytepos+1;
    846.             fillbits.val = (1<<(bytepos+1))-1;
    847.             writeBits(fillbits);
    848.         }
    849.  
    850.         writeWord(0xFFD9); //EOI
    851.         //return byteout;
    852.         isDone = true;
    853.     }
    854. }
     
  14. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,104
    This is cool. I'm trying to get a C# jpeg decoder... I am going to try to reverse this code to be a decoder.
     
  15. tonemcbride

    tonemcbride

    Joined:
    Sep 7, 2010
    Posts:
    973
    I converted the previous javascript version to C# if anyone's interested. I've put it here: http://bit.ly/jpgencoder

    There's some code at the bottom of the file on how to use it :)

    Tony
     
  16. lexvandersluijs

    lexvandersluijs

    Joined:
    Jul 1, 2011
    Posts:
    7
    @Matthey and @Tony,

    Thanks for the great work, it works perfectly!

    Lex
     
  17. AndreasBroager

    AndreasBroager

    Joined:
    Jun 7, 2013
    Posts:
    3
    Hi

    I have ported the encoder to C# (threaded version)... With some minor tweaks... e.g if you want to save the image to disc, it can be done in the encoding thread.

    It is also possible to use the encoder as a blocking method (like texture.EncodeToPNG).

    Find the source here: Github.com Version1

    You only need the JPGEncoder.cs
    For the different usage see TestEncode.cs

    Note: Please only use the one with tag "Version1" (the URL above) as there is no guarantee if the trunk even compiles...

    Edit: Moved repo to github
     
    Last edited: Feb 26, 2016
    Meltdown likes this.
  18. miroslavpasaliski

    miroslavpasaliski

    Joined:
    Nov 30, 2017
    Posts:
    1
    Hi Guys,

    if you need online solutio for converting PNG to JPG files, you can use this online tool. http://pngtojpg.org/
     
  19. ZhengzhongSun

    ZhengzhongSun

    Joined:
    Oct 12, 2016
    Posts:
    20
    Code (CSharp):
    1. // Create a texture. Texture size does not matter, since
    2. // LoadImage will replace with with incoming image size.
    3. Texture2D tex = new Texture2D(2, 2);
    4. // Load data into the texture.
    5. tex.LoadImage(pngBytes);
    6. byte[] jpgBytes = tex.EncodeToJPG();
    You can try this method, I hope it will be helpful to you!
     
    Tokars likes this.
  20. tonemcbride

    tonemcbride

    Joined:
    Sep 7, 2010
    Posts:
    973
    Thanks @ZhengzhongSun , the original code was done before Unity added support for encoding to jpeg.
     
unityunity