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
46 image-rendering: pixelated;
47 image-rendering: crisp-edges;
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;
65 #clearimagesbutton,#cleartextbutton {
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>
128 <button id="cleartextbutton">♻ Clear text</button>
132 <div id="zoomandclear">
134 <select id="zoomselect">
135 <option value="1" selected>1x</option>
136 <option value="2">2x</option>
137 <option value="4">4x</option>
138 <option value="8">8x</option>
139 <option value="16">16x</option>
140 <option value="32">32x</option>
142 <button id="clearimagesbutton">♻ Clear images</button>
145 <div id="images"></div>
148 // Returns zoom factor as a number.
149 var getSelectedZoom = function () {
150 return new Number(document.getElementById("zoomselect").value);
153 // Scales a given image with the selected zoom factor.
154 var scaleSingleImage = function (img) {
155 var factor = getSelectedZoom();
156 img.style.width = (img.naturalWidth * factor) + "px";
157 img.style.height = (img.naturalHeight * factor) + "px";
160 // Rescales all <img> elements in the page. Used after changing the selected zoom.
161 var rescaleImages = function () {
162 var imageList = document.getElementsByTagName("img");
163 for (var i = 0; i < imageList.length; i++) {
164 scaleSingleImage(imageList[i])
168 // Removes everything contained in the images <div>.
169 var clearImages = function () {
170 var imagesNode = document.getElementById("images");
171 while (imagesNode.hasChildNodes()) {
172 imagesNode.removeChild(imagesNode.lastChild);
176 // Clears textarea text.
177 var clearText = function() {
178 document.getElementById("qpatext").value = "";
181 // Returns a properly sized image with the given base64-encoded PNG data.
182 var createImage = function (pngData, imageName) {
183 var imageContainer = document.createElement("figure");
184 if (imageName.length > 0) {
185 var newFileNameHeader = document.createElement("figcaption");
186 newFileNameHeader.textContent = escape(imageName);
187 imageContainer.appendChild(newFileNameHeader);
189 var newImage = document.createElement("img");
190 newImage.src = "data:image/png;base64," + pngData;
191 newImage.onload = (function () {
192 // Grab the image for the callback. We need to wait until
193 // the image has been properly loaded to access its
194 // naturalWidth and naturalHeight properties, needed for
196 var cbImage = newImage;
198 scaleSingleImage(cbImage);
201 imageContainer.appendChild(newImage);
202 return imageContainer;
205 // Returns a new h3 header with the given file name.
206 var createFileNameHeader = function (fileName) {
207 var newHeader = document.createElement("h3");
208 newHeader.textContent = fileName;
212 // Returns a new image block to contain images from a file.
213 var createImagesBlock = function () {
214 var imagesBlock = document.createElement("div");
215 imagesBlock.className = "imagesblock";
219 // Processes a chunk of QPA text from the given file name. Creates
220 // the file name header and a list of images in the images <div>, as
221 // found in the text.
222 var processText = function(textString, fileName) {
223 var imagesNode = document.getElementById("images");
224 var newHeader = createFileNameHeader(fileName);
225 imagesNode.appendChild(newHeader);
226 var imagesBlock = createImagesBlock();
227 // [\s\S] is a match-anything regexp like the dot, except it
228 // also matches newlines. Ideally, browsers would need to widely
229 // support the "dotall" regexp modifier, but that's not the case
230 // yet and this does the trick.
231 // Group 1 are the image element properties, if any.
232 // Group 2 is the base64 PNG data.
233 var imageRegexp = /<Image\b(.*?)>([\s\S]*?)<\/Image>/g;
234 var imageNameRegexp = /\bName="(.*?)"/;
238 while ((result = imageRegexp.exec(textString)) !== null) {
239 innerResult = result[1].match(imageNameRegexp);
240 imageName = ((innerResult !== null) ? innerResult[1] : "");
241 // Blanks need to be removed from the base64 string.
242 var pngData = result[2].replace(/\s+/g, "");
243 imagesBlock.appendChild(createImage(pngData, imageName));
245 imagesNode.appendChild(imagesBlock);
248 // Loads images from the text in the text area.
249 var loadImages = function () {
250 processText(document.getElementById("qpatext").value, "<Pasted Text>");
253 // Loads images from the files in the file selector.
254 var handleFileSelect = function (evt) {
255 var files = evt.target.files;
256 for (var i = 0; i < files.length; i++) {
257 // Creates a reader per file.
258 var reader = new FileReader();
259 // Grab the needed objects to use them after the file has
260 // been read, in order to process its contents and add
261 // images, if found, in the images <div>.
262 reader.onload = (function () {
263 var cbFileName = files[i].name;
264 var cbReader = reader;
266 processText(cbReader.result, cbFileName);
269 // Reads file contents. This will trigger the event above.
270 reader.readAsText(files[i]);
274 // File selector trick: click on the selector when clicking on the
276 var clickFileSelector = function () {
277 document.getElementById("fileselector").click();
280 // Clears selected files to be able to select them again if needed.
281 var clearSelectedFiles = function() {
282 document.getElementById("fileselector").value = "";
285 // Set event handlers for interactive elements in the page.
286 document.getElementById("fileselector").onclick = clearSelectedFiles;
287 document.getElementById("fileselector").addEventListener("change", handleFileSelect, false);
288 document.getElementById("fileselectorbutton").onclick = clickFileSelector;
289 document.getElementById("loadimagesbutton").onclick = loadImages;
290 document.getElementById("cleartextbutton").onclick = clearText;
291 document.getElementById("zoomselect").onchange = rescaleImages;
292 document.getElementById("clearimagesbutton").onclick = clearImages;