• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!--
2--------------------------------------
3HTML QPA Image Viewer
4--------------------------------------
5
6Copyright (c) 2020 The Khronos Group Inc.
7Copyright (c) 2020 Valve Corporation.
8
9Licensed under the Apache License, Version 2.0 (the "License");
10you may not use this file except in compliance with the License.
11You may obtain a copy of the License at
12
13http://www.apache.org/licenses/LICENSE-2.0
14
15Unless required by applicable law or agreed to in writing, software
16distributed under the License is distributed on an "AS IS" BASIS,
17WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18See the License for the specific language governing permissions and
19limitations under the License.
20-->
21<html>
22    <head>
23        <meta charset="utf-8"/>
24        <title>Load PNGs from QPA output</title>
25        <style>
26            body {
27                background: white;
28                text-align: left;
29                font-family: sans-serif;
30            }
31            h1 {
32                margin-top: 2ex;
33            }
34            h2 {
35                font-size: large;
36            }
37            figure {
38                display: flex;
39                flex-direction: column;
40            }
41            img {
42                margin-right: 1ex;
43                margin-bottom: 1ex;
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. */
51                background: black;
52            }
53            button {
54                margin: 1ex;
55                border: none;
56                border-radius: .5ex;
57                padding: 1ex;
58                background-color: steelblue;
59                color: white;
60                font-size: large;
61            }
62            button:hover {
63                opacity: .8;
64            }
65            #clearimagesbutton {
66                background-color: seagreen;
67            }
68            select {
69                font-size: large;
70                padding: 1ex;
71                border-radius: .5ex;
72                border: 1px solid darkgrey;
73            }
74            select:hover {
75                opacity: .8;
76            }
77            .loadoption {
78                text-align: center;
79                margin: 1ex;
80                padding: 2ex;
81                border: 1px solid darkgrey;
82                border-radius: 1ex;
83            }
84            #options {
85                display: flex;
86                flex-wrap: wrap;
87            }
88            #qpatext {
89                display: block;
90                min-width: 80ex;
91                max-width: 132ex;
92                min-height: 25ex;
93                max-height: 25ex;
94                margin: 1ex auto;
95            }
96            #fileselector {
97                display: none;
98            }
99            #zoomandclear {
100                margin: 2ex;
101            }
102            #images {
103                margin: 2ex;
104                display: flex;
105                flex-direction: column;
106            }
107            .imagesblock {
108                display: flex;
109                flex-wrap: wrap;
110            }
111        </style>
112    </head>
113    <body>
114        <h1>Load PNGs from QPA output</h1>
115
116        <div id="options">
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">&#x1F4C2; Load files</button>
121                <input id="fileselector" type="file" multiple>
122            </div>
123
124            <div class="loadoption">
125                <h2>Option 2: Paste QPA text or text extract containing &lt;Image&gt; elements below and click "Load images"</h2>
126                <textarea id="qpatext"></textarea>
127                <button id="loadimagesbutton">&#x1F4C3; Load images</button>
128            </div>
129        </div>
130
131        <div id="zoomandclear">
132            &#x1F50E; Image zoom
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>
140            </select>
141            <button id="clearimagesbutton">&#x267B; Clear images</button>
142        </div>
143
144        <div id="images"></div>
145
146        <script>
147            // Returns zoom factor as a number.
148            var getSelectedZoom = function () {
149                return new Number(document.getElementById("zoomselect").value);
150            }
151
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";
157            }
158
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])
164                }
165            }
166
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);
172                }
173            }
174
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);
182                }
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
189                    // scaling.
190                    var cbImage = newImage;
191                    return function () {
192                        scaleSingleImage(cbImage);
193                    };
194                })();
195                imageContainer.appendChild(newImage);
196                return imageContainer;
197            }
198
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;
203                return newHeader;
204            }
205
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";
210                return imagesBlock;
211            }
212
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="(.*?)"/;
229                var result;
230                var innerResult;
231                var imageName;
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));
238                }
239                imagesNode.appendChild(imagesBlock);
240            }
241
242            // Loads images from the text in the text area.
243            var loadImages = function () {
244                processText(document.getElementById("qpatext").value, "<Pasted Text>");
245            }
246
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;
259                        return function () {
260                            processText(cbReader.result, cbFileName);
261                        };
262                    })();
263                    // Reads file contents. This will trigger the event above.
264                    reader.readAsText(files[i]);
265                }
266            }
267
268            // File selector trick: click on the selector when clicking on the
269            // custom button.
270            var clickFileSelector = function () {
271                document.getElementById("fileselector").click();
272            }
273
274            // Clears selected files to be able to select them again if needed.
275            var clearSelectedFiles = function() {
276                document.getElementById("fileselector").value = "";
277            }
278
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;
286        </script>
287    </body>
288</html>
289