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.javascript.host.canvas;
16  
17  import java.nio.ByteBuffer;
18  
19  import org.htmlunit.corejs.javascript.Context;
20  import org.htmlunit.corejs.javascript.Function;
21  import org.htmlunit.corejs.javascript.ScriptableObject;
22  import org.htmlunit.corejs.javascript.VarScope;
23  import org.htmlunit.corejs.javascript.typedarrays.NativeArrayBuffer;
24  import org.htmlunit.corejs.javascript.typedarrays.NativeUint8ClampedArray;
25  import org.htmlunit.javascript.HtmlUnitScriptable;
26  import org.htmlunit.javascript.JavaScriptEngine;
27  import org.htmlunit.javascript.configuration.JsxClass;
28  import org.htmlunit.javascript.configuration.JsxConstructor;
29  import org.htmlunit.javascript.configuration.JsxGetter;
30  import org.htmlunit.javascript.host.dom.DOMException;
31  import org.htmlunit.platform.canvas.rendering.RenderingBackend;
32  
33  /**
34   * A JavaScript object for {@code ImageData}.
35   *
36   * @author Ahmed Ashour
37   * @author Ronald Brill
38   *
39   * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/ImageData">MDN Documentation</a>
40   */
41  @JsxClass
42  public class ImageData extends HtmlUnitScriptable {
43  
44      private final byte[] bytes_;
45      private final int width_;
46      private final int height_;
47      private NativeUint8ClampedArray data_;
48  
49      /**
50       * Default constructor.
51       */
52      public ImageData() {
53          this(null, 0, 0, 0, 0);
54      }
55  
56      /**
57       * JavaScript constructor.
58       * @param cx the current context
59       * @param scope the scope
60       * @param args the arguments to the ImageData constructor
61       * @param ctorObj the function object
62       * @param inNewExpr {@code true} if invoked with the {@code new} operator
63       * @return the Java object that JavaScript can access
64       */
65      @JsxConstructor
66      public static ImageData jsConstructor(final Context cx, final VarScope scope,
67              final Object[] args, final Function ctorObj, final boolean inNewExpr) {
68          if (args.length < 2) {
69              throw JavaScriptEngine.typeError("ImageData ctor - too less arguments");
70          }
71  
72          NativeUint8ClampedArray data = null;
73          final int width;
74          final int height;
75          if (args[0] instanceof NativeUint8ClampedArray array) {
76              data = array;
77              if (data.getArrayLength() % 4 != 0) {
78                  throw JavaScriptEngine.asJavaScriptException(
79                          (HtmlUnitScriptable) getTopLevelScope(scope).getGlobalThis(),
80                          "ImageData ctor - data length mod 4 not zero",
81                          DOMException.INVALID_STATE_ERR);
82              }
83  
84              width = (int) JavaScriptEngine.toInteger(args[1]);
85              if (args.length < 3) {
86                  height = data.getArrayLength() / 4 / width;
87  
88                  if (data.getArrayLength() != 4 * width * height) {
89                      throw JavaScriptEngine.asJavaScriptException(
90                              (HtmlUnitScriptable) getTopLevelScope(scope).getGlobalThis(),
91                              "ImageData ctor - width not correct",
92                              DOMException.INDEX_SIZE_ERR);
93                  }
94              }
95              else {
96                  height = (int) JavaScriptEngine.toInteger(args[2]);
97              }
98  
99              if (data.getArrayLength() != 4 * width * height) {
100                 throw JavaScriptEngine.asJavaScriptException(
101                         (HtmlUnitScriptable) getTopLevelScope(scope).getGlobalThis(),
102                         "ImageData ctor - width/height not correct",
103                         DOMException.INDEX_SIZE_ERR);
104             }
105         }
106         else {
107             width = (int) JavaScriptEngine.toInteger(args[0]);
108             height = (int) JavaScriptEngine.toInteger(args[1]);
109         }
110 
111         if (width < 0) {
112             throw JavaScriptEngine.asJavaScriptException(
113                     (HtmlUnitScriptable) getTopLevelScope(scope).getGlobalThis(),
114                     "ImageData ctor - width negative",
115                     DOMException.INDEX_SIZE_ERR);
116         }
117         if (height < 0) {
118             throw JavaScriptEngine.asJavaScriptException(
119                     (HtmlUnitScriptable) getTopLevelScope(scope).getGlobalThis(),
120                     "ImageData ctor - height negative",
121                     DOMException.INDEX_SIZE_ERR);
122         }
123 
124         final ImageData result = new ImageData(null, 0, 0, width, height);
125         if (data != null) {
126             final ByteBuffer buffer = data.getBuffer().getByteBuffer();
127             final int bytesToCopy = Math.min(buffer.remaining(), result.bytes_.length);
128             buffer.get(result.bytes_, 0, bytesToCopy);
129         }
130         return result;
131     }
132 
133     ImageData(final RenderingBackend context, final int x, final int y, final int width, final int height) {
134         super();
135         if (context == null) {
136             bytes_ = new byte[width * height * 4];
137         }
138         else {
139             bytes_ = context.getBytes(width, height, x, y);
140         }
141 
142         width_ = width;
143         height_ = height;
144     }
145 
146     /**
147      * Returns the {@code width} property.
148      * @return the {@code width} property
149      */
150     @JsxGetter
151     public int getWidth() {
152         return width_;
153     }
154 
155     /**
156      * Returns the {@code height} property.
157      * @return the {@code height} property
158      */
159     @JsxGetter
160     public int getHeight() {
161         return height_;
162     }
163 
164     /**
165      * Returns a {@link NativeUint8ClampedArray} representing a one-dimensional array containing
166      * the data in the RGBA order, with integer values between 0 and 255 (included).
167      * @return the {@code data} property
168      */
169     @JsxGetter
170     public NativeUint8ClampedArray getData() {
171         if (data_ == null) {
172             final NativeArrayBuffer arrayBuffer = new NativeArrayBuffer(bytes_.length);
173             arrayBuffer.getByteBuffer().put(bytes_);
174 
175             data_ = new NativeUint8ClampedArray(arrayBuffer, 0, bytes_.length);
176             data_.setParentScope(getParentScope());
177             data_.setPrototype(ScriptableObject.getClassPrototype(getParentScope(), data_.getClassName()));
178         }
179 
180         return data_;
181     }
182 
183 }