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