Skip to main content

RE: [JAI] Improving speed of scaling (was Re: GIF image corrupt and Re: why is my performance...)

3 replies [Last post]
Anonymous

Thomas,

I think your difficulty stems from not understanding the JAI
"pull" model well enough. This is something a lot of people
including myself get confused by.

Your assumption that JAI.create("stream", ...) reads the image
into memory is not quite right, and that's at least the start
of your problems.

The key point to be made is this: When you perform a

image = JAI.create("operation", pb, ...);

you are generally NOT doing any computation. You are creating
a node in the operation chain, in which the input is hooked
up to a new instance of your operation, and the output is
returned to you as a RenderedOp.

The data in a RenderedOp is not computed until you request it.
So let's say you did

RenderedOp inputImage = JAI.create("stream", ...);

the inputImage will not contain any pixel data to start.
If you then pass this inputImage to another operation, the
same thing holds. If you construct a long chain of operations,
then call getTiles() on the final output image, the getTiles()
will result in a series of calls back through the chain to
retrieve whatever tiles are needed for the output image.

So, if you did

PlanarImage loadImage = JAI.create("stream", ...);
PlanarImage cropImage = JAI.create("crop", pb, ...);
PlanarImage scaleImage = JAI.create("scale", pb, ...);

Then no image data is computed. If you then called scaleImage.getTiles()
and timed this method, the total time would be the time needed to
compute
the entire chain, not just the scale. In this case, crop is not really
a computational operator but a filter that adjusts the reported size
of its output image, so the timing info would account for both the time
needed to read the file and the time to scale the image.

Notice that all of this is based around tiles as the sort of "currency"
that is passed around between operations. If your source image is
untiled,
i.e. has only one tile, then any subsequent operators in the chain will
eventually call getTile(0,0), which will read your entire image into
memory. Everything that you've described seems consistent with this.
Try to find out whether your TIFF is tiled by using tiffinfo (a quick
google search for tiffinfo will get you started in the right direction).

As for how to read tiles from an untiled image... well it MAY (only may)
be possible to pass a RenderingHints into the Stream operator that
specifies the tiling. And the TIFF reader may (just may) be able to
take advantage of this to retile your image from source. But I wouldn't
get too hopeful.

The way to do this would be something like

ImageLayout layout = new ImageLayout();
layout.setTileWidth(tWidth);
layout.setTileHeight(tHeight);
RenderingHints hints = new RenderingHints();
hints.put(JAI.KEY_IMAGE_LAYOUT, layout);
inputImage = JAI.create("stream", pb, hints);

I think that's how it works, it's been a while.

You might also try the JAI ImageIO tools TIFF reader. From what I
understand,
it's better than the JAI codec in several ways. And you may be able to
use the
ImageRead operator and try something similar to what I showed above. Or
you
may not. worth a try though.

Mike

> -----Original Message-----
> From: Thomas Wolf [mailto:twolf@ithaka.org]
> Sent: Tuesday, July 05, 2005 11:17 AM
> To: interest@jai.dev.java.net
> Subject: [JAI] Improving speed of scaling (was Re: GIF image
> corrupt and Re: why is my performance...)
>
>
> Hi Mike,
>
> Nidel, Mike wrote:
> ...
> > So you are cropping, scaling, then writing a JPEG from a 200MB TIFF
> > file. This brings us to the perennial question: is your
> source image
> > tiled? If not, then as soon as you ask for a single pixel, you will
> > probably end up reading the entire image into memory. If
> this is the
> > case, then I would say that reading 200MB into main memory
> in 5 sec is
> > probably not slow at all.
>
> I don't think the source TIFFs are "tiled" - although I
> haven't got a clue on how to check that for sure (as you may
> have been able to tell from my posts, I'm not that versed in
> imaging :-) But I expected the whole image to be read into
> memory anyway - and I think the "reading into memory" is a
> one-time cost incurred with this call:
> workingImage = JAI.create("stream", stream);
> This method only gets called the first time the front-end
> requests a part of the image. After that, I retrieve a
> reference to this source image via the session and simply do
> the cropping and scaling operations and output as a JPEG.
>
> So, from my perspective, I can understand a 5sec initial
> delay as a 200mb image is read in. But subsequent operations
> should be much faster. But they aren't. I think I'm missing
> something important here :-(
>
> After getting James' suggestion regarding timing the operations,
> long t0 = System.currentTimeMillis();
> workingImage = JAI.create("scale", pb);
> workingImage.getTiles(); // force to compute
> long t1 = System.currentTimeMillis();
> System.out.println("Time spent in scale= "+(t1-t0));
> I now see that what I thought was a 5+ second encoding to
> JPEG is actually less than 500ms. Scaling seems to take
> 4.5seconds! But I'm a little
> confused: I thought - ok, if getTiles() forces computation
> for the scale operation, perhaps I will have to do the same
> for the crop operation to get an accurate timing of it! But
> when I do getTiles() after the crop operation, it turns into
> another 4.5sec operation (for a total time of
> 10+sec!) So, obviously doing a getTiles() on the crop
> operation to get
> more 'accurate' timing wasn't helping things. So I'm more
> than a bit confused as to timing things properly.
>
> Anyway, assuming that my 200mb TIFF source image is not tiled
> and that I always want to crop the image and output to a JPEG
> of size 700x"some- height-that-maintains-aspect-ratio", what
> is the optimal way to go? (The source images are
> high-resolution plant specimen images.)
>
> Thanks everyone for your patience in answering my questions. Tom
>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
> For additional commands, e-mail: interest-help@jai.dev.java.net
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
For additional commands, e-mail: interest-help@jai.dev.java.net

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Bob Deen

>Thanks a bunch for this good explanation of what is happening. But if I
>have this large, untiled* TIFF image
>on a server and I get numerous requests to "see" parts of it in a
>browser, what is the best way to serve these
>sub-images - maybe I'm doing it wrong by using the crop and scale
>operations (and trying to keep original
>image in memory)? You mentioned re-tiling the image. Are you
>suggesting that retiling the untiled image and
>saving that retiled image in memory (or writing it to disk and let
>subsequent requests read the retiled image)
>might speed things up?

I've not been paying that close attention... so sorry if this is not what
you're looking for. But it seems like what you should do here is to have
a load operator with enough memory in its tile cache to hold the entire
image (as you say)... followed by a format operator, with a more reasonable
tile size in its rendering hints.

The thing is, if your tiles are the size of the entire image, then in order
to compute anything you'll need an *additional* chunk of memory the size of
the entire image in order to hold the output of the operator. And maybe
a third for subsequent ops.

By having just one copy in memory then setting up a smaller tile size, each
subsequent op can work much more efficiently, on only a piece at a time,
greatly reducing memory requirements. Plus, if your end use is something
like a display where only part of the image needs to be computed, then only
part of the image WILL be computed. With image-size tiles, the entire image
must be computed regardless.

>278 Rows Per Strip SHORT 1 9969

Bingo... there's the proof it's untiled. That equals the number of rows.

-Bob

---------------------------------------------------------------------
To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
For additional commands, e-mail: interest-help@jai.dev.java.net

Thomas Wolf

Hi Mike,
Thanks a bunch for this good explanation of what is happening. But if I
have this large, untiled* TIFF image
on a server and I get numerous requests to "see" parts of it in a
browser, what is the best way to serve these
sub-images - maybe I'm doing it wrong by using the crop and scale
operations (and trying to keep original
image in memory)? You mentioned re-tiling the image. Are you
suggesting that retiling the untiled image and
saving that retiled image in memory (or writing it to disk and let
subsequent requests read the retiled image)
might speed things up?

Thanks again for the guidance.
tom

-------------------------------TIFF image info for one of my images
-----------------
* Below is the output of a tiff viewer (sorry, I couldn't get tiffinfo
to work on my XP box), but it seems
to indicate a non-tiled image.)

Page 1 of 1
IFD OFFSET = 8
The number of tags = 19

254 New Subfile Type LONG 1 0 (0 Hex)
256 Image Width SHORT 1 7000
257 Image Length SHORT 1 9969
258 Bits Per Sample SHORT 3 <242> 8 8 8
259 Compression SHORT 1 1
262 Photometric SHORT 1 2
273 Strip Offsets LONG 1 16516 (4084 Hex)
277 Samples Per Pixel SHORT 1 3
278 Rows Per Strip SHORT 1 9969
279 Strip Byte Counts LONG 1 209349000 (c7a6988 Hex)
282 X Resolution RATIONAL 1 <248> 6000000 / 10000 = 600.000
283 Y Resolution RATIONAL 1 <256> 6000000 / 10000 = 600.000
284 Planar Configuration SHORT 1 1
296 Resolution Unit SHORT 1 2
305 Software ASCII 29 <264> Adobe Photoshop
Elements 2.0
306 Date Time ASCII 20 <294> 2004:07:21 12:32:47
700 Unknown Tag BYTE 5036 <314>
34377 Unknown Tag BYTE 11166 <5350>
34665 Unknown Tag LONG 1 209365516 (c7aaa0c Hex)

Nidel, Mike wrote:

>Thomas,
>
>I think your difficulty stems from not understanding the JAI
>"pull" model well enough. This is something a lot of people
>including myself get confused by.
>
>Your assumption that JAI.create("stream", ...) reads the image
>into memory is not quite right, and that's at least the start
>of your problems.
>
>The key point to be made is this: When you perform a
>
>image = JAI.create("operation", pb, ...);
>
>you are generally NOT doing any computation. You are creating
>a node in the operation chain, in which the input is hooked
>up to a new instance of your operation, and the output is
>returned to you as a RenderedOp.
>
>The data in a RenderedOp is not computed until you request it.
>So let's say you did
>
>RenderedOp inputImage = JAI.create("stream", ...);
>
>the inputImage will not contain any pixel data to start.
>If you then pass this inputImage to another operation, the
>same thing holds. If you construct a long chain of operations,
>then call getTiles() on the final output image, the getTiles()
>will result in a series of calls back through the chain to
>retrieve whatever tiles are needed for the output image.
>
>So, if you did
>
>PlanarImage loadImage = JAI.create("stream", ...);
>PlanarImage cropImage = JAI.create("crop", pb, ...);
>PlanarImage scaleImage = JAI.create("scale", pb, ...);
>
>Then no image data is computed. If you then called scaleImage.getTiles()
>and timed this method, the total time would be the time needed to
>compute
>the entire chain, not just the scale. In this case, crop is not really
>a computational operator but a filter that adjusts the reported size
>of its output image, so the timing info would account for both the time
>needed to read the file and the time to scale the image.
>
>Notice that all of this is based around tiles as the sort of "currency"
>that is passed around between operations. If your source image is
>untiled,
>i.e. has only one tile, then any subsequent operators in the chain will
>eventually call getTile(0,0), which will read your entire image into
>memory. Everything that you've described seems consistent with this.
>Try to find out whether your TIFF is tiled by using tiffinfo (a quick
>google search for tiffinfo will get you started in the right direction).
>
>As for how to read tiles from an untiled image... well it MAY (only may)
>be possible to pass a RenderingHints into the Stream operator that
>specifies the tiling. And the TIFF reader may (just may) be able to
>take advantage of this to retile your image from source. But I wouldn't
>get too hopeful.
>
>The way to do this would be something like
>
>ImageLayout layout = new ImageLayout();
>layout.setTileWidth(tWidth);
>layout.setTileHeight(tHeight);
>RenderingHints hints = new RenderingHints();
>hints.put(JAI.KEY_IMAGE_LAYOUT, layout);
>inputImage = JAI.create("stream", pb, hints);
>
>I think that's how it works, it's been a while.
>
>
>You might also try the JAI ImageIO tools TIFF reader. From what I
>understand,
>it's better than the JAI codec in several ways. And you may be able to
>use the
>ImageRead operator and try something similar to what I showed above. Or
>you
>may not. worth a try though.
>
>Mike
>
>
>
>
>>-----Original Message-----
>>From: Thomas Wolf [mailto:twolf@ithaka.org]
>>Sent: Tuesday, July 05, 2005 11:17 AM
>>To: interest@jai.dev.java.net
>>Subject: [JAI] Improving speed of scaling (was Re: GIF image
>>corrupt and Re: why is my performance...)
>>
>>
>>Hi Mike,
>>
>>Nidel, Mike wrote:
>>...
>>
>>
>>>So you are cropping, scaling, then writing a JPEG from a 200MB TIFF
>>>file. This brings us to the perennial question: is your
>>>
>>>
>>source image
>>
>>
>>>tiled? If not, then as soon as you ask for a single pixel, you will
>>>probably end up reading the entire image into memory. If
>>>
>>>
>>this is the
>>
>>
>>>case, then I would say that reading 200MB into main memory
>>>
>>>
>>in 5 sec is
>>
>>
>>>probably not slow at all.
>>>
>>>
>>I don't think the source TIFFs are "tiled" - although I
>>haven't got a clue on how to check that for sure (as you may
>>have been able to tell from my posts, I'm not that versed in
>>imaging :-) But I expected the whole image to be read into
>>memory anyway - and I think the "reading into memory" is a
>>one-time cost incurred with this call:
>> workingImage = JAI.create("stream", stream);
>>This method only gets called the first time the front-end
>>requests a part of the image. After that, I retrieve a
>>reference to this source image via the session and simply do
>>the cropping and scaling operations and output as a JPEG.
>>
>>So, from my perspective, I can understand a 5sec initial
>>delay as a 200mb image is read in. But subsequent operations
>>should be much faster. But they aren't. I think I'm missing
>>something important here :-(
>>
>>After getting James' suggestion regarding timing the operations,
>> long t0 = System.currentTimeMillis();
>> workingImage = JAI.create("scale", pb);
>> workingImage.getTiles(); // force to compute
>> long t1 = System.currentTimeMillis();
>> System.out.println("Time spent in scale= "+(t1-t0));
>>I now see that what I thought was a 5+ second encoding to
>>JPEG is actually less than 500ms. Scaling seems to take
>>4.5seconds! But I'm a little
>>confused: I thought - ok, if getTiles() forces computation
>>for the scale operation, perhaps I will have to do the same
>>for the crop operation to get an accurate timing of it! But
>>when I do getTiles() after the crop operation, it turns into
>>another 4.5sec operation (for a total time of
>>10+sec!) So, obviously doing a getTiles() on the crop
>>operation to get
>>more 'accurate' timing wasn't helping things. So I'm more
>>than a bit confused as to timing things properly.
>>
>>Anyway, assuming that my 200mb TIFF source image is not tiled
>>and that I always want to crop the image and output to a JPEG
>>of size 700x"some- height-that-maintains-aspect-ratio", what
>>is the optimal way to go? (The source images are
>>high-resolution plant specimen images.)
>>
>>Thanks everyone for your patience in answering my questions. Tom
>>
>>
>>
>>---------------------------------------------------------------------
>>To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
>>For additional commands, e-mail: interest-help@jai.dev.java.net
>>
>>
>>
>>
>
>---------------------------------------------------------------------
>To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
>For additional commands, e-mail: interest-help@jai.dev.java.net
>
>

---------------------------------------------------------------------
To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
For additional commands, e-mail: interest-help@jai.dev.java.net

Thomas Wolf

Ahem, sorry for the mangled text - my email client was setup was screwy.
Here's the tiff info again, hopefully with formatting preserved.
tom

Page 1 of 1
IFD OFFSET = 8
The number of tags = 19

254 New Subfile Type LONG 1 0 (0 Hex)
256 Image Width SHORT 1 7000
257 Image Length SHORT 1 9969
258 Bits Per Sample SHORT 3 <242> 8 8 8
259 Compression SHORT 1 1
262 Photometric SHORT 1 2
273 Strip Offsets LONG 1 16516 (4084 Hex)
277 Samples Per Pixel SHORT 1 3
278 Rows Per Strip SHORT 1 9969
279 Strip Byte Counts LONG 1 209349000 (c7a6988 Hex)
282 X Resolution RATIONAL 1 <248> 6000000 / 10000 = 600.000
283 Y Resolution RATIONAL 1 <256> 6000000 / 10000 = 600.000
284 Planar Configuration SHORT 1 1
296 Resolution Unit SHORT 1 2
305 Software ASCII 29 <264> Adobe Photoshop Elements 2.0
306 Date Time ASCII 20 <294> 2004:07:21 12:32:47
700 Unknown Tag BYTE 5036 <314>
34377 Unknown Tag BYTE 11166 <5350>
34665 Unknown Tag LONG 1 209365516 (c7aaa0c Hex)

Thomas Wolf wrote:
> Hi Mike,
> Thanks a bunch for this good explanation of what is happening. But if I
> have this large, untiled* TIFF image
> on a server and I get numerous requests to "see" parts of it in a
> browser, what is the best way to serve these
> sub-images - maybe I'm doing it wrong by using the crop and scale
> operations (and trying to keep original
> image in memory)? You mentioned re-tiling the image. Are you
> suggesting that retiling the untiled image and
> saving that retiled image in memory (or writing it to disk and let
> subsequent requests read the retiled image)
> might speed things up?
>
> Thanks again for the guidance.
> tom
>
>
> -------------------------------TIFF image info for one of my images
> -----------------
> * Below is the output of a tiff viewer (sorry, I couldn't get tiffinfo
> to work on my XP box), but it seems
> to indicate a non-tiled image.)
>
> Page 1 of 1
> IFD OFFSET = 8
> The number of tags = 19
>
> 254 New Subfile Type LONG 1 0 (0 Hex)
> 256 Image Width SHORT 1 7000 257 Image Length
> SHORT 1 9969 258 Bits Per Sample SHORT 3 <242>
> 8 8 8 259 Compression SHORT 1 1 262
> Photometric SHORT 1 2 273 Strip Offsets
> LONG 1 16516 (4084 Hex)
> 277 Samples Per Pixel SHORT 1 3 278 Rows Per Strip
> SHORT 1 9969 279 Strip Byte Counts LONG 1
> 209349000 (c7a6988 Hex)
> 282 X Resolution RATIONAL 1 <248> 6000000 / 10000 = 600.000
> 283 Y Resolution RATIONAL 1 <256> 6000000 / 10000 = 600.000
> 284 Planar Configuration SHORT 1 1 296 Resolution Unit
> SHORT 1 2 305 Software ASCII 29 <264>
> Adobe Photoshop Elements 2.0
> 306 Date Time ASCII 20 <294> 2004:07:21 12:32:47
> 700 Unknown Tag BYTE 5036 <314> 34377 Unknown
> Tag BYTE 11166 <5350> 34665 Unknown Tag LONG
> 1 209365516 (c7aaa0c Hex)
>
>
>
> Nidel, Mike wrote:
>
>> Thomas,
>>
>> I think your difficulty stems from not understanding the JAI
>> "pull" model well enough. This is something a lot of people
>> including myself get confused by.
>>
>> Your assumption that JAI.create("stream", ...) reads the image
>> into memory is not quite right, and that's at least the start
>> of your problems.
>>
>> The key point to be made is this: When you perform a
>>
>> image = JAI.create("operation", pb, ...);
>>
>> you are generally NOT doing any computation. You are creating
>> a node in the operation chain, in which the input is hooked
>> up to a new instance of your operation, and the output is
>> returned to you as a RenderedOp.
>>
>> The data in a RenderedOp is not computed until you request it.
>> So let's say you did
>>
>> RenderedOp inputImage = JAI.create("stream", ...);
>>
>> the inputImage will not contain any pixel data to start.
>> If you then pass this inputImage to another operation, the
>> same thing holds. If you construct a long chain of operations,
>> then call getTiles() on the final output image, the getTiles()
>> will result in a series of calls back through the chain to
>> retrieve whatever tiles are needed for the output image.
>>
>> So, if you did
>>
>> PlanarImage loadImage = JAI.create("stream", ...);
>> PlanarImage cropImage = JAI.create("crop", pb, ...);
>> PlanarImage scaleImage = JAI.create("scale", pb, ...);
>>
>> Then no image data is computed. If you then called scaleImage.getTiles()
>> and timed this method, the total time would be the time needed to
>> compute
>> the entire chain, not just the scale. In this case, crop is not really
>> a computational operator but a filter that adjusts the reported size
>> of its output image, so the timing info would account for both the time
>> needed to read the file and the time to scale the image.
>>
>> Notice that all of this is based around tiles as the sort of "currency"
>> that is passed around between operations. If your source image is
>> untiled,
>> i.e. has only one tile, then any subsequent operators in the chain will
>> eventually call getTile(0,0), which will read your entire image into
>> memory. Everything that you've described seems consistent with this.
>> Try to find out whether your TIFF is tiled by using tiffinfo (a quick
>> google search for tiffinfo will get you started in the right direction).
>>
>> As for how to read tiles from an untiled image... well it MAY (only may)
>> be possible to pass a RenderingHints into the Stream operator that
>> specifies the tiling. And the TIFF reader may (just may) be able to
>> take advantage of this to retile your image from source. But I wouldn't
>> get too hopeful.
>>
>> The way to do this would be something like
>>
>> ImageLayout layout = new ImageLayout();
>> layout.setTileWidth(tWidth);
>> layout.setTileHeight(tHeight);
>> RenderingHints hints = new RenderingHints();
>> hints.put(JAI.KEY_IMAGE_LAYOUT, layout);
>> inputImage = JAI.create("stream", pb, hints);
>>
>> I think that's how it works, it's been a while.
>>
>>
>> You might also try the JAI ImageIO tools TIFF reader. From what I
>> understand,
>> it's better than the JAI codec in several ways. And you may be able to
>> use the
>> ImageRead operator and try something similar to what I showed above. Or
>> you
>> may not. worth a try though.
>>
>> Mike
>>
>>
>>
>>
>>> -----Original Message-----
>>> From: Thomas Wolf [mailto:twolf@ithaka.org] Sent: Tuesday, July 05,
>>> 2005 11:17 AM
>>> To: interest@jai.dev.java.net
>>> Subject: [JAI] Improving speed of scaling (was Re: GIF image corrupt
>>> and Re: why is my performance...)
>>>
>>>
>>> Hi Mike,
>>>
>>> Nidel, Mike wrote:
>>> ...
>>>
>>>
>>>> So you are cropping, scaling, then writing a JPEG from a 200MB TIFF
>>>> file. This brings us to the perennial question: is your
>>>
>>> source image
>>>
>>>> tiled? If not, then as soon as you ask for a single pixel, you will
>>>> probably end up reading the entire image into memory. If
>>>
>>> this is the
>>>
>>>> case, then I would say that reading 200MB into main memory
>>>
>>> in 5 sec is
>>>
>>>> probably not slow at all.
>>>>
>>>
>>> I don't think the source TIFFs are "tiled" - although I haven't got a
>>> clue on how to check that for sure (as you may have been able to tell
>>> from my posts, I'm not that versed in imaging :-) But I expected the
>>> whole image to be read into memory anyway - and I think the "reading
>>> into memory" is a one-time cost incurred with this call:
>>> workingImage = JAI.create("stream", stream);
>>> This method only gets called the first time the front-end requests a
>>> part of the image. After that, I retrieve a reference to this source
>>> image via the session and simply do the cropping and scaling
>>> operations and output as a JPEG.
>>>
>>> So, from my perspective, I can understand a 5sec initial delay as a
>>> 200mb image is read in. But subsequent operations should be much
>>> faster. But they aren't. I think I'm missing something important
>>> here :-(
>>>
>>> After getting James' suggestion regarding timing the operations,
>>> long t0 = System.currentTimeMillis();
>>> workingImage = JAI.create("scale", pb);
>>> workingImage.getTiles(); // force to compute
>>> long t1 = System.currentTimeMillis();
>>> System.out.println("Time spent in scale= "+(t1-t0));
>>> I now see that what I thought was a 5+ second encoding to JPEG is
>>> actually less than 500ms. Scaling seems to take 4.5seconds! But I'm
>>> a little
>>> confused: I thought - ok, if getTiles() forces computation for the
>>> scale operation, perhaps I will have to do the same for the crop
>>> operation to get an accurate timing of it! But when I do getTiles()
>>> after the crop operation, it turns into another 4.5sec operation (for
>>> a total time of
>>> 10+sec!) So, obviously doing a getTiles() on the crop operation to get
>>> more 'accurate' timing wasn't helping things. So I'm more than a bit
>>> confused as to timing things properly.
>>>
>>> Anyway, assuming that my 200mb TIFF source image is not tiled and
>>> that I always want to crop the image and output to a JPEG of size
>>> 700x"some- height-that-maintains-aspect-ratio", what is the optimal
>>> way to go? (The source images are high-resolution plant specimen
>>> images.)
>>>
>>> Thanks everyone for your patience in answering my questions. Tom
>>>
>>>
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
>>> For additional commands, e-mail: interest-help@jai.dev.java.net
>>>
>>>
>>>
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
>> For additional commands, e-mail: interest-help@jai.dev.java.net
>>
>>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
> For additional commands, e-mail: interest-help@jai.dev.java.net

---------------------------------------------------------------------
To unsubscribe, e-mail: interest-unsubscribe@jai.dev.java.net
For additional commands, e-mail: interest-help@jai.dev.java.net