View Javadoc
1   /*
2    * Copyright (c) 2002-2026 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.platform.image;
16  
17  import java.io.IOException;
18  import java.io.InputStream;
19  import java.lang.ref.Cleaner;
20  import java.util.Iterator;
21  
22  import javax.imageio.ImageIO;
23  import javax.imageio.ImageReader;
24  import javax.imageio.stream.ImageInputStream;
25  
26  import org.htmlunit.WebClient;
27  import org.htmlunit.platform.geom.IntDimension2D;
28  
29  /**
30   * <span style="color:red">INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.</span><br>
31   *
32   * Wraps the ImageReader for an HtmlImage. This is necessary because an object with a finalize()
33   * method is only garbage collected after the method has been run. Which causes all referenced
34   * objects to also not be garbage collected until this happens. Because a HtmlImage references a lot
35   * of objects which could all be garbage collected without impacting the ImageReader it is better to
36   * wrap it in another class.
37   *
38   * @author Mike Bowler
39   * @author David K. Taylor
40   * @author Christian Sell
41   * @author Ahmed Ashour
42   * @author Knut Johannes Dahle
43   * @author Ronald Brill
44   * @author Frank Danek
45   * @author Carsten Steul
46   * @author Alex Gorbatovsky
47   */
48  public class ImageIOImageData implements ImageData {
49  
50      // private static final Log LOG = LogFactory.getLog(ImageIOImageData.class);
51  
52      private IntDimension2D dim_;
53  
54      private final ImageIOImageDataCleaningAction cleaningAction_;
55      private final Cleaner.Cleanable cleanable_;
56  
57      private static final class ImageIOImageDataCleaningAction implements Runnable {
58          private ImageReader imageReader_;
59          private InputStream inputStream_;
60  
61          ImageIOImageDataCleaningAction(final ImageReader imageReader, final InputStream inputStream) {
62              imageReader_ = imageReader;
63              inputStream_ = inputStream;
64          }
65  
66          synchronized ImageReader getImageReader() {
67              if (imageReader_ == null) {
68                  throw new IllegalStateException("ImageIOImageData is closed");
69              }
70              return imageReader_;
71          }
72  
73          @Override
74          public synchronized void run() {
75              if (imageReader_ == null) {
76                  return;
77              }
78  
79              try (ImageInputStream stream = (ImageInputStream) imageReader_.getInput()) {
80                  // nothing
81              }
82              catch (final IOException e) {
83                  // optionally log
84              }
85              finally {
86                  imageReader_.setInput(null);
87                  imageReader_.dispose();
88                  imageReader_ = null;
89              }
90  
91              try {
92                  inputStream_.close();
93              }
94              catch (final IOException e) {
95                  // optionally log
96              }
97              finally {
98                  inputStream_ = null;
99              }
100         }
101     }
102 
103     /**
104      * Ctor.
105      * @param inputStream the {@link InputStream} to read from
106      * @throws IOException in case of error
107      */
108     public ImageIOImageData(final InputStream inputStream) throws IOException {
109         final ImageInputStream iis = ImageIO.createImageInputStream(inputStream);
110         final Iterator<ImageReader> iter = ImageIO.getImageReaders(iis);
111         if (!iter.hasNext()) {
112             iis.close();
113             inputStream.close();
114             throw new IOException("No image detected in response");
115         }
116 
117         final ImageReader imageReader = iter.next();
118         imageReader.setInput(iis);
119 
120         cleaningAction_ = new ImageIOImageDataCleaningAction(imageReader, inputStream);
121         cleanable_ = WebClient.registerCleanerAction(this, cleaningAction_);
122 
123         // dispose all others
124         while (iter.hasNext()) {
125             iter.next().dispose();
126         }
127     }
128 
129     /**
130      * Returns the {@link ImageReader}.
131      *
132      * @return the {@link ImageReader}
133      */
134     public ImageReader getImageReader() {
135         return cleaningAction_.getImageReader();
136     }
137 
138     /**
139      * {@inheritDoc}
140      */
141     @Override
142     public IntDimension2D getWidthHeight() throws IOException {
143         if (dim_ == null) {
144             final ImageReader imgReader = cleaningAction_.getImageReader();
145             dim_ = new IntDimension2D(imgReader.getWidth(0), imgReader.getHeight(0));
146         }
147         return dim_;
148     }
149 
150     /**
151      * {@inheritDoc}
152      */
153     @Override
154     public void close() {
155         cleanable_.clean();
156     }
157 }