Java-Gaming.org Hi !
Featured games (91)
games approved by the League of Dukes
Games in Showcase (799)
Games in Android Showcase (236)
games submitted by our members
Games in WIP (865)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  Read PNG/JPEG/BMP/GIF dimensions without loading image data  (Read 11924 times)
0 Members and 1 Guest are viewing this topic.
Offline Riven
Administrator

« JGO Overlord »


Medals: 1370
Projects: 4
Exp: 16 years


Hand over your head.


« Posted 2009-10-26 14:47:18 »

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
   public static Dimension getImageDimension(File file) throws IOException
   {
      RandomAccessFile raf = new RandomAccessFile(file, "r");
     
      try
      {
         int header = raf.readUnsignedShort();

         if (header == 0x8950)
         {
            // PNG!
            raf.seek(8 + 4 + 4); // thanks, Abuse

            return new Dimension(raf.readInt(), raf.readInt());
         }

         if (header == 0xffd8)
         {
            // JPG!

            // (see below)
         }
         else if (header == 0x424D)
         {
            // BMP!
            raf.seek(0x0012);

            int w = raf.read() | (raf.read() << 8) | (raf.read() << 16) | (raf.read() << 24);
            int h = raf.read() | (raf.read() << 8) | (raf.read() << 16) | (raf.read() << 24);
            return new Dimension(w, h);
         }
         else if (header == (('G' << 8) | ('I' << 0)))
         {
            // GIF!
            raf.seek(0x0006);
            int w = raf.read() | (raf.read() << 8);
            int h = raf.read() | (raf.read() << 8);
            return new Dimension(w, h);
         }
         else
         {
            throw new IllegalStateException("unexpected header: " + Integer.toHexString(header));
         }

         while (true) // JPG is not so easy...
         {
            int marker = raf.readUnsignedShort();

            switch (marker)
            {
               case 0xffd8: // SOI
               case 0xffd0: // RST0
               case 0xffd1: // RST1
               case 0xffd2: // RST2
               case 0xffd3: // RST3
               case 0xffd4: // RST4
               case 0xffd5: // RST5
               case 0xffd6: // RST6
               case 0xffd7: // RST7
               case 0xffd9: // EOI
                  break;

               case 0xffdd: // DRI
                  raf.readUnsignedShort();
                  break;

               case 0xffe0: // APP0
               case 0xffe1: // APP1
               case 0xffe2: // APP2
               case 0xffe3: // APP3
               case 0xffe4: // APP4
               case 0xffe5: // APP5
               case 0xffe6: // APP6
               case 0xffe7: // APP7
               case 0xffe8: // APP8
               case 0xffe9: // APP9
               case 0xffea: // APPa
               case 0xffeb: // APPb
               case 0xffec: // APPc
               case 0xffed: // APPd
               case 0xffee: // APPe
               case 0xffef: // APPf
               case 0xfffe: // COM
               case 0xffdb: // DQT
               case 0xffc4: // DHT
               case 0xffda: // SOS
                  raf.readFully(new byte[raf.readUnsignedShort() - 2]);
                  break;

               case 0xffc0: // SOF0
               case 0xffc2: // SOF2
                  raf.readUnsignedShort();
                  raf.readByte();
                  int height = raf.readUnsignedShort();
                  int width = raf.readUnsignedShort();
                  return new Dimension(width, height);

               default:
                  throw new IllegalStateException("invalid jpg marker: " + Integer.toHexString(marker));
            }
         }
      }
      finally
      {
         raf.close();
      }
   }
}

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Offline Riven
Administrator

« JGO Overlord »


Medals: 1370
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #1 - Posted 2009-10-26 15:33:40 »

This version works on InputStreams too, which is required for loading your images through a classloader.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
72  
73  
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88  
89  
90  
91  
92  
93  
94  
95  
96  
97  
98  
99  
100  
101  
102  
103  
104  
105  
106  
107  
108  
109  
110  
111  
112  
   public static Dimension getImageDimension(File file) throws IOException
   {
      return ImageUtil.getImageDimension(new FileInputStream(file));
   }

   public static Dimension getImageDimension(InputStream in) throws IOException
   {
      DataInputStream dis = new DataInputStream(in);

      try
      {
         int header = dis.readUnsignedShort();

         if (header == 0x8950)
         {
            // PNG!
            dis.readFully(new byte[(8 - 2) + 4 + 4]); // thanks Abuse

            return new Dimension(dis.readInt(), dis.readInt());
         }

         if (header == 0xffd8)
         {
            // JPG!

            // (see below)
         }
         else if (header == 0x424D)
         {
            // BMP!
            dis.readFully(new byte[16]);

            int w = dis.read() | (dis.read() << 8) | (dis.read() << 16) | (dis.read() << 24);
            int h = dis.read() | (dis.read() << 8) | (dis.read() << 16) | (dis.read() << 24);
            return new Dimension(w, h);
         }
         else if (header == (('G' << 8) | ('I' << 0))) // GIF
         {
            // GIF!
            dis.readFully(new byte[4]);
            int w = dis.read() | (dis.read() << 8);
            int h = dis.read() | (dis.read() << 8);
            return new Dimension(w, h);
         }
         else
         {
            throw new IllegalStateException("unexpected header: " + Integer.toHexString(header));
         }

         while (true)
         {
            int marker = dis.readUnsignedShort();

            switch (marker)
            {
               case 0xffd8: // SOI
               case 0xffd0: // RST0
               case 0xffd1: // RST1
               case 0xffd2: // RST2
               case 0xffd3: // RST3
               case 0xffd4: // RST4
               case 0xffd5: // RST5
               case 0xffd6: // RST6
               case 0xffd7: // RST7
               case 0xffd9: // EOI
                  break;

               case 0xffdd: // DRI
                  dis.readUnsignedShort();
                  break;

               case 0xffe0: // APP0
               case 0xffe1: // APP1
               case 0xffe2: // APP2
               case 0xffe3: // APP3
               case 0xffe4: // APP4
               case 0xffe5: // APP5
               case 0xffe6: // APP6
               case 0xffe7: // APP7
               case 0xffe8: // APP8
               case 0xffe9: // APP9
               case 0xffea: // APPa
               case 0xffeb: // APPb
               case 0xffec: // APPc
               case 0xffed: // APPd
               case 0xffee: // APPe
               case 0xffef: // APPf
               case 0xfffe: // COM
               case 0xffdb: // DQT
               case 0xffc4: // DHT
               case 0xffda: // SOS
                  dis.readFully(new byte[dis.readUnsignedShort() - 2]);
                  break;

               case 0xffc0: // SOF0
               case 0xffc2: // SOF2
                  dis.readUnsignedShort();
                  dis.readByte();
                  int height = dis.readUnsignedShort();
                  int width = dis.readUnsignedShort();
                  return new Dimension(width, height);

               default:
                  throw new IllegalStateException("invalid jpg marker: " + Integer.toHexString(marker));
            }
         }
      }
      finally
      {
         dis.close();
      }
   }

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Offline Abuse

JGO Ninja


Medals: 71


falling into the abyss of reality


« Reply #2 - Posted 2009-10-26 17:11:10 »

Wouldn't it be nice if DataInput defined methods for reading primitives in LSB order as well as Java's chosen convention of MSB.

It seems such a simple yet incredibly useful thing to have missed off; and given the choice of zip for code package distribution it must have been an I/O necessity from the very beginning! (DEFLATE stores block lengths in LSB order)
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Riven
Administrator

« JGO Overlord »


Medals: 1370
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #3 - Posted 2009-10-26 21:31:12 »

Yup. Judging by how the naming convetions at Sun are (LayoutManager => LayoutManager2, NIO => NIO2) we could expect DataInput2 any day now.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Offline CaptainJester

JGO Knight


Medals: 12
Projects: 2
Exp: 14 years


Make it work; make it better.


« Reply #4 - Posted 2009-10-27 12:17:33 »

Wouldn't it be nice if DataInput defined methods for reading primitives in LSB order as well as Java's chosen convention of MSB.

It seems such a simple yet incredibly useful thing to have missed off; and given the choice of zip for code package distribution it must have been an I/O necessity from the very beginning! (DEFLATE stores block lengths in LSB order)

You could use a FileChannel.  Then when you create your ByteBuffer for reading from the channel you set the order to ByteOrder.LITTLE_ENDIAN or ByteOrder.BIG_ENDIAN.  Then from the ByteBuffer getFloat,getInt,getLong etc...

Offline Riven
Administrator

« JGO Overlord »


Medals: 1370
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #5 - Posted 2009-10-27 12:35:44 »

Yeah... but I'm working with InputStreams...

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Offline CommanderKeith
« Reply #6 - Posted 2009-10-28 12:24:49 »

This is very useful, thanks.  May I ask, what are you using it for?

Offline Riven
Administrator

« JGO Overlord »


Medals: 1370
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #7 - Posted 2009-10-28 13:47:25 »

I have this webservice, that must scale huge images (8K * 4K pixels) to thumbnails. I use this code to determine whether the webservice (which has a small heap to keep FullGC down to a minimum) can handle it, or it should launch a new JVM to do it -- which might run out of memory, without taking down the webservice.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Offline Abuse

JGO Ninja


Medals: 71


falling into the abyss of reality


« Reply #8 - Posted 2009-10-28 19:54:05 »

I have this webservice, that must scale huge images (8K * 4K pixels) to thumbnails. I use this code to determine whether the webservice (which has a small heap to keep FullGC down to a minimum) can handle it, or it should launch a new JVM to do it -- which might run out of memory, without taking down the webservice.

Sounds like what you really want is a progressive decoder for all of said image formats  Wink
Offline Riven
Administrator

« JGO Overlord »


Medals: 1370
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #9 - Posted 2009-10-28 21:32:19 »

Indeed. Me lazy.

BMP and PNG (RGB24/ARGB32) are trivial. JPG not so simple. JAI did not do it (dispite the promise), and I didn't want to spend more time on it, so this was a quick hack, that will probably be used for a few years. persecutioncomplex

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline CaptainJester

JGO Knight


Medals: 12
Projects: 2
Exp: 14 years


Make it work; make it better.


« Reply #10 - Posted 2009-10-29 14:49:36 »

Yeah... but I'm working with InputStreams...
Feed the bytes into a ByteBuffer yourself.  Then you can still take advantage of the ByteOrder of a ByteBuffer.

Offline CommanderKeith
« Reply #11 - Posted 2009-10-29 16:46:27 »

I have this webservice, that must scale huge images (8K * 4K pixels) to thumbnails. I use this code to determine whether the webservice (which has a small heap to keep FullGC down to a minimum) can handle it, or it should launch a new JVM to do it -- which might run out of memory, without taking down the webservice.
Interesting. Clever solution. How annoying that JAI api can't do it.

Offline Riven
Administrator

« JGO Overlord »


Medals: 1370
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #12 - Posted 2009-10-29 18:00:03 »

Feed the bytes into a ByteBuffer yourself.  Then you can still take advantage of the ByteOrder of a ByteBuffer.

And the resulting code would have been... better?

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Offline pjt33

« JGO Spiffy Duke »


Medals: 40
Projects: 4
Exp: 7 years



« Reply #13 - Posted 2009-10-30 00:22:05 »

Indeed. Me lazy.

BMP and PNG (RGB24/ARGB32) are trivial. JPG not so simple. JAI did not do it (dispite the promise), and I didn't want to spend more time on it, so this was a quick hack, that will probably be used for a few years. persecutioncomplex
JAI does it for TIFFs - I've used it to process a 22k x 22k image without exceeding 64MB heap. Not sure how you were going about it, but what I used was

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
ImageReader ir=ImageIO.getImageReadersByFormatName("tiff").next(); // Hacky
ImageInputStream iis=new FileImageInputStream(new RandomAccessFile(file,"r"));
ir.setInput(iis);
for (y) {
    for (x) {
        ImageReadParam irp=new ImageReadParam();
        irp.setSourceRegion(new Rectangle(x,y,w,h));
        BufferedImage bi=ir.read(0,irp);
        // Do stuff with bi
    }
}


If Sun's JPG reader doesn't handle that then there must be a third party one which does, although finding and getting it approved for use will, of course, be much hassle.
Offline Riven
Administrator

« JGO Overlord »


Medals: 1370
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #14 - Posted 2009-10-30 09:02:13 »

Thanks, turned out I used JAI totally different, and appearantly that resulted in loading the whole image.

Anyway, I got it to work with JPG (trivial, with your code sample), but man.. it's slow!


the image is 8K*4K

ImageIO.read(new File("something.jpg")); // 3sec

the code below takes almost 40 (!!) seconds. Just the loading of the tiles, not even scaling! So I'm going to stick with my current (external JVM) solution.


1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
      ImageReader ir = ImageIO.getImageReadersByFormatName("jpeg").next();
      ImageInputStream iis = new FileImageInputStream(new RandomAccessFile(file, "r"));
      ir.setInput(iis);

      int w = ir.getWidth(0);
      int h = ir.getHeight(0);

      int dim = 512;

      for (int y = 0; y < h - dim; y += dim)
      {
         for (int x = 0; x < w - dim; x += dim)
         {
            ImageReadParam irp = new ImageReadParam();
            irp.setSourceRegion(new Rectangle(x, y, dim, dim));
            BufferedImage bi = ir.read(0, irp);
            bi.flush();
         }
      }

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings!
Pages: [1]
  ignore  |  Print  
 
 

 
Riven (185 views)
2019-09-04 15:33:17

hadezbladez (4863 views)
2018-11-16 13:46:03

hadezbladez (1785 views)
2018-11-16 13:41:33

hadezbladez (5135 views)
2018-11-16 13:35:35

hadezbladez (1015 views)
2018-11-16 13:32:03

EgonOlsen (4367 views)
2018-06-10 19:43:48

EgonOlsen (5215 views)
2018-06-10 19:43:44

EgonOlsen (2944 views)
2018-06-10 19:43:20

DesertCoockie (3867 views)
2018-05-13 18:23:11

nelsongames (4293 views)
2018-04-24 18:15:36
Java Gaming Resources
by philfrei
2019-05-14 16:15:13

Deployment and Packaging
by philfrei
2019-05-08 15:15:36

Deployment and Packaging
by philfrei
2019-05-08 15:13:34

Deployment and Packaging
by philfrei
2019-02-17 20:25:53

Deployment and Packaging
by mudlee
2018-08-22 18:09:50

Java Gaming Resources
by gouessej
2018-08-22 08:19:41

Deployment and Packaging
by gouessej
2018-08-22 08:04:08

Deployment and Packaging
by gouessej
2018-08-22 08:03:45
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!