2 --------------------------------------
4 --------------------------------------
6 Copyright (c) 2020 The Khronos Group Inc.
7 Copyright (c) 2020 Valve Corporation.
9 Licensed under the Apache License, Version 2.0 (the "License");
10 you may not use this file except in compliance with the License.
11 You may obtain a copy of the License at
13 http://www.apache.org/licenses/LICENSE-2.0
15 Unless required by applicable law or agreed to in writing, software
16 distributed under the License is distributed on an "AS IS" BASIS,
17 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18 See the License for the specific language governing permissions and
19 limitations under the License.
23 <meta charset="utf-8"/>
24 <title>Load PNGs from QPA output</title>
29 font-family: sans-serif;
39 flex-direction: column;
44 /* Attempt to zoom images using the nearest-neighbor scaling
45 algorithm. Unfortunately, not supported under Firefox at the
46 time this text is being written. */
47 image-rendering: pixelated;
48 /* Use a black background color for images in case some pixels
49 are transparent to some degree. In the worst case, the image
50 could appear to be missing. */
58 background-color: steelblue;
66 background-color: seagreen;
72 border: 1px solid darkgrey;
81 border: 1px solid darkgrey;
105 flex-direction: column;
114 <h1>Load PNGs from QPA output</h1>
117 <div class="loadoption">
118 <h2>Option 1: Load local QPA files</h2>
119 <!-- The file selector text cannot be changed, so we use a hidden selector trick. -->
120 <button id="fileselectorbutton">📂 Load files</button>
121 <input id="fileselector" type="file" multiple>
124 <div class="loadoption">
125 <h2>Option 2: Paste QPA text or text extract containing <Image> elements below and click "Load images"</h2>
126 <textarea id="qpatext"></textarea>
127 <button id="loadimagesbutton">📃 Load images</button>
131 <div id="zoomandclear">
133 <select id="zoomselect">
134 <option value="1" selected>1x</option>
135 <option value="2">2x</option>
136 <option value="4">4x</option>
137 <option value="8">8x</option>
138 <option value="16">16x</option>
139 <option value="32">32x</option>
141 <button id="clearimagesbutton">♻ Clear images</button>
144 <div id="images"></div>
147 // Returns zoom factor as a number.
148 var getSelectedZoom = function () {
149 return new Number(document.getElementById("zoomselect").value);
152 // Scales a given image with the selected zoom factor.
153 var scaleSingleImage = function (img) {
154 var factor = getSelectedZoom();
155 img.style.width = (img.naturalWidth * factor) + "px";
156 img.style.height = (img.naturalHeight * factor) + "px";
159 // Rescales all <img> elements in the page. Used after changing the selected zoom.
160 var rescaleImages = function () {
161 var imageList = document.getElementsByTagName("img");
162 for (var i = 0; i < imageList.length; i++) {
163 scaleSingleImage(imageList[i])
167 // Removes everything contained in the images <div>.
168 var clearImages = function () {
169 var imagesNode = document.getElementById("images");
170 while (imagesNode.hasChildNodes()) {
171 imagesNode.removeChild(imagesNode.lastChild);
175 // Returns a properly sized image with the given base64-encoded PNG data.
176 var createImage = function (pngData, imageName) {
177 var imageContainer = document.createElement("figure");
178 if (imageName.length > 0) {
179 var newFileNameHeader = document.createElement("figcaption");
180 newFileNameHeader.textContent = escape(imageName);
181 imageContainer.appendChild(newFileNameHeader);
183 var newImage = document.createElement("img");
184 newImage.src = "data:image/png;base64," + pngData;
185 newImage.onload = (function () {
186 // Grab the image for the callback. We need to wait until
187 // the image has been properly loaded to access its
188 // naturalWidth and naturalHeight properties, needed for
190 var cbImage = newImage;
192 scaleSingleImage(cbImage);
195 imageContainer.appendChild(newImage);
196 return imageContainer;
199 // Returns a new h3 header with the given file name.
200 var createFileNameHeader = function (fileName) {
201 var newHeader = document.createElement("h3");
202 newHeader.textContent = fileName;
206 // Returns a new image block to contain images from a file.
207 var createImagesBlock = function () {
208 var imagesBlock = document.createElement("div");
209 imagesBlock.className = "imagesblock";
213 // Processes a chunk of QPA text from the given file name. Creates
214 // the file name header and a list of images in the images <div>, as
215 // found in the text.
216 var processText = function(textString, fileName) {
217 var imagesNode = document.getElementById("images");
218 var newHeader = createFileNameHeader(fileName);
219 imagesNode.appendChild(newHeader);
220 var imagesBlock = createImagesBlock();
221 // [\s\S] is a match-anything regexp like the dot, except it
222 // also matches newlines. Ideally, browsers would need to widely
223 // support the "dotall" regexp modifier, but that's not the case
224 // yet and this does the trick.
225 // Group 1 are the image element properties, if any.
226 // Group 2 is the base64 PNG data.
227 var imageRegexp = /<Image\b(.*?)>([\s\S]*?)<\/Image>/g;
228 var imageNameRegexp = /\bName="(.*?)"/;
232 while ((result = imageRegexp.exec(textString)) !== null) {
233 innerResult = result[1].match(imageNameRegexp);
234 imageName = ((innerResult !== null) ? innerResult[1] : "");
235 // Blanks need to be removed from the base64 string.
236 var pngData = result[2].replace(/\s+/g, "");
237 imagesBlock.appendChild(createImage(pngData, imageName));
239 imagesNode.appendChild(imagesBlock);
242 // Loads images from the text in the text area.
243 var loadImages = function () {
244 processText(document.getElementById("qpatext").value, "<Pasted Text>");
247 // Loads images from the files in the file selector.
248 var handleFileSelect = function (evt) {
249 var files = evt.target.files;
250 for (var i = 0; i < files.length; i++) {
251 // Creates a reader per file.
252 var reader = new FileReader();
253 // Grab the needed objects to use them after the file has
254 // been read, in order to process its contents and add
255 // images, if found, in the images <div>.
256 reader.onload = (function () {
257 var cbFileName = files[i].name;
258 var cbReader = reader;
260 processText(cbReader.result, cbFileName);
263 // Reads file contents. This will trigger the event above.
264 reader.readAsText(files[i]);
268 // File selector trick: click on the selector when clicking on the
270 var clickFileSelector = function () {
271 document.getElementById("fileselector").click();
274 // Clears selected files to be able to select them again if needed.
275 var clearSelectedFiles = function() {
276 document.getElementById("fileselector").value = "";
279 // Set event handlers for interactive elements in the page.
280 document.getElementById("fileselector").onclick = clearSelectedFiles;
281 document.getElementById("fileselector").addEventListener("change", handleFileSelect, false);
282 document.getElementById("fileselectorbutton").onclick = clickFileSelector;
283 document.getElementById("loadimagesbutton").onclick = loadImages;
284 document.getElementById("zoomselect").onchange = rescaleImages;
285 document.getElementById("clearimagesbutton").onclick = clearImages;