1e5c31af7Sopenharmony_ci<!--
2e5c31af7Sopenharmony_ci--------------------------------------
3e5c31af7Sopenharmony_ciHTML QPA Image Viewer
4e5c31af7Sopenharmony_ci--------------------------------------
5e5c31af7Sopenharmony_ci
6e5c31af7Sopenharmony_ciCopyright (c) 2020 The Khronos Group Inc.
7e5c31af7Sopenharmony_ciCopyright (c) 2020 Valve Corporation.
8e5c31af7Sopenharmony_ci
9e5c31af7Sopenharmony_ciLicensed under the Apache License, Version 2.0 (the "License");
10e5c31af7Sopenharmony_ciyou may not use this file except in compliance with the License.
11e5c31af7Sopenharmony_ciYou may obtain a copy of the License at
12e5c31af7Sopenharmony_ci
13e5c31af7Sopenharmony_cihttp://www.apache.org/licenses/LICENSE-2.0
14e5c31af7Sopenharmony_ci
15e5c31af7Sopenharmony_ciUnless required by applicable law or agreed to in writing, software
16e5c31af7Sopenharmony_cidistributed under the License is distributed on an "AS IS" BASIS,
17e5c31af7Sopenharmony_ciWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18e5c31af7Sopenharmony_ciSee the License for the specific language governing permissions and
19e5c31af7Sopenharmony_cilimitations under the License.
20e5c31af7Sopenharmony_ci-->
21e5c31af7Sopenharmony_ci<html>
22e5c31af7Sopenharmony_ci    <head>
23e5c31af7Sopenharmony_ci        <meta charset="utf-8"/>
24e5c31af7Sopenharmony_ci        <title>Load PNGs from QPA output</title>
25e5c31af7Sopenharmony_ci        <style>
26e5c31af7Sopenharmony_ci            body {
27e5c31af7Sopenharmony_ci                background: white;
28e5c31af7Sopenharmony_ci                text-align: left;
29e5c31af7Sopenharmony_ci                font-family: sans-serif;
30e5c31af7Sopenharmony_ci            }
31e5c31af7Sopenharmony_ci            h1 {
32e5c31af7Sopenharmony_ci                margin-top: 2ex;
33e5c31af7Sopenharmony_ci            }
34e5c31af7Sopenharmony_ci            h2 {
35e5c31af7Sopenharmony_ci                font-size: large;
36e5c31af7Sopenharmony_ci            }
37e5c31af7Sopenharmony_ci            figure {
38e5c31af7Sopenharmony_ci                display: flex;
39e5c31af7Sopenharmony_ci                flex-direction: column;
40e5c31af7Sopenharmony_ci            }
41e5c31af7Sopenharmony_ci            img {
42e5c31af7Sopenharmony_ci                margin-right: 1ex;
43e5c31af7Sopenharmony_ci                margin-bottom: 1ex;
44e5c31af7Sopenharmony_ci                /* Attempt to zoom images using the nearest-neighbor scaling
45e5c31af7Sopenharmony_ci                algorithm. */
46e5c31af7Sopenharmony_ci                image-rendering: pixelated;
47e5c31af7Sopenharmony_ci                image-rendering: crisp-edges;
48e5c31af7Sopenharmony_ci                /* Use a black background color for images in case some pixels
49e5c31af7Sopenharmony_ci                are transparent to some degree. In the worst case, the image
50e5c31af7Sopenharmony_ci                could appear to be missing. */
51e5c31af7Sopenharmony_ci                background: black;
52e5c31af7Sopenharmony_ci            }
53e5c31af7Sopenharmony_ci            button {
54e5c31af7Sopenharmony_ci                margin: 1ex;
55e5c31af7Sopenharmony_ci                border: none;
56e5c31af7Sopenharmony_ci                border-radius: .5ex;
57e5c31af7Sopenharmony_ci                padding: 1ex;
58e5c31af7Sopenharmony_ci                background-color: steelblue;
59e5c31af7Sopenharmony_ci                color: white;
60e5c31af7Sopenharmony_ci                font-size: large;
61e5c31af7Sopenharmony_ci            }
62e5c31af7Sopenharmony_ci            button:hover {
63e5c31af7Sopenharmony_ci                opacity: .8;
64e5c31af7Sopenharmony_ci            }
65e5c31af7Sopenharmony_ci            #clearimagesbutton,#cleartextbutton {
66e5c31af7Sopenharmony_ci                background-color: seagreen;
67e5c31af7Sopenharmony_ci            }
68e5c31af7Sopenharmony_ci            select {
69e5c31af7Sopenharmony_ci                font-size: large;
70e5c31af7Sopenharmony_ci                padding: 1ex;
71e5c31af7Sopenharmony_ci                border-radius: .5ex;
72e5c31af7Sopenharmony_ci                border: 1px solid darkgrey;
73e5c31af7Sopenharmony_ci            }
74e5c31af7Sopenharmony_ci            select:hover {
75e5c31af7Sopenharmony_ci                opacity: .8;
76e5c31af7Sopenharmony_ci            }
77e5c31af7Sopenharmony_ci            .loadoption {
78e5c31af7Sopenharmony_ci                text-align: center;
79e5c31af7Sopenharmony_ci                margin: 1ex;
80e5c31af7Sopenharmony_ci                padding: 2ex;
81e5c31af7Sopenharmony_ci                border: 1px solid darkgrey;
82e5c31af7Sopenharmony_ci                border-radius: 1ex;
83e5c31af7Sopenharmony_ci            }
84e5c31af7Sopenharmony_ci            #options {
85e5c31af7Sopenharmony_ci                display: flex;
86e5c31af7Sopenharmony_ci                flex-wrap: wrap;
87e5c31af7Sopenharmony_ci            }
88e5c31af7Sopenharmony_ci            #qpatext {
89e5c31af7Sopenharmony_ci                display: block;
90e5c31af7Sopenharmony_ci                min-width: 80ex;
91e5c31af7Sopenharmony_ci                max-width: 132ex;
92e5c31af7Sopenharmony_ci                min-height: 25ex;
93e5c31af7Sopenharmony_ci                max-height: 25ex;
94e5c31af7Sopenharmony_ci                margin: 1ex auto;
95e5c31af7Sopenharmony_ci            }
96e5c31af7Sopenharmony_ci            #fileselector {
97e5c31af7Sopenharmony_ci                display: none;
98e5c31af7Sopenharmony_ci            }
99e5c31af7Sopenharmony_ci            #zoomandclear {
100e5c31af7Sopenharmony_ci                margin: 2ex;
101e5c31af7Sopenharmony_ci            }
102e5c31af7Sopenharmony_ci            #images {
103e5c31af7Sopenharmony_ci                margin: 2ex;
104e5c31af7Sopenharmony_ci                display: flex;
105e5c31af7Sopenharmony_ci                flex-direction: column;
106e5c31af7Sopenharmony_ci            }
107e5c31af7Sopenharmony_ci            .imagesblock {
108e5c31af7Sopenharmony_ci                display: flex;
109e5c31af7Sopenharmony_ci                flex-wrap: wrap;
110e5c31af7Sopenharmony_ci            }
111e5c31af7Sopenharmony_ci        </style>
112e5c31af7Sopenharmony_ci    </head>
113e5c31af7Sopenharmony_ci    <body>
114e5c31af7Sopenharmony_ci        <h1>Load PNGs from QPA output</h1>
115e5c31af7Sopenharmony_ci
116e5c31af7Sopenharmony_ci        <div id="options">
117e5c31af7Sopenharmony_ci            <div class="loadoption">
118e5c31af7Sopenharmony_ci                <h2>Option 1: Load local QPA files</h2>
119e5c31af7Sopenharmony_ci                <!-- The file selector text cannot be changed, so we use a hidden selector trick. -->
120e5c31af7Sopenharmony_ci                <button id="fileselectorbutton">&#x1F4C2; Load files</button>
121e5c31af7Sopenharmony_ci                <input id="fileselector" type="file" multiple>
122e5c31af7Sopenharmony_ci            </div>
123e5c31af7Sopenharmony_ci
124e5c31af7Sopenharmony_ci            <div class="loadoption">
125e5c31af7Sopenharmony_ci                <h2>Option 2: Paste QPA text or text extract containing &lt;Image&gt; elements below and click "Load images"</h2>
126e5c31af7Sopenharmony_ci                <textarea id="qpatext"></textarea>
127e5c31af7Sopenharmony_ci                <button id="loadimagesbutton">&#x1F4C3; Load images</button>
128e5c31af7Sopenharmony_ci                <button id="cleartextbutton">&#x267B; Clear text</button>
129e5c31af7Sopenharmony_ci            </div>
130e5c31af7Sopenharmony_ci        </div>
131e5c31af7Sopenharmony_ci
132e5c31af7Sopenharmony_ci        <div id="zoomandclear">
133e5c31af7Sopenharmony_ci            &#x1F50E; Image zoom
134e5c31af7Sopenharmony_ci            <select id="zoomselect">
135e5c31af7Sopenharmony_ci                <option value="1" selected>1x</option>
136e5c31af7Sopenharmony_ci                <option value="2">2x</option>
137e5c31af7Sopenharmony_ci                <option value="4">4x</option>
138e5c31af7Sopenharmony_ci                <option value="8">8x</option>
139e5c31af7Sopenharmony_ci                <option value="16">16x</option>
140e5c31af7Sopenharmony_ci                <option value="32">32x</option>
141e5c31af7Sopenharmony_ci            </select>
142e5c31af7Sopenharmony_ci            <button id="clearimagesbutton">&#x267B; Clear images</button>
143e5c31af7Sopenharmony_ci        </div>
144e5c31af7Sopenharmony_ci
145e5c31af7Sopenharmony_ci        <div id="images"></div>
146e5c31af7Sopenharmony_ci
147e5c31af7Sopenharmony_ci        <script>
148e5c31af7Sopenharmony_ci            // Returns zoom factor as a number.
149e5c31af7Sopenharmony_ci            var getSelectedZoom = function () {
150e5c31af7Sopenharmony_ci                return new Number(document.getElementById("zoomselect").value);
151e5c31af7Sopenharmony_ci            }
152e5c31af7Sopenharmony_ci
153e5c31af7Sopenharmony_ci            // Scales a given image with the selected zoom factor.
154e5c31af7Sopenharmony_ci            var scaleSingleImage = function (img) {
155e5c31af7Sopenharmony_ci                var factor = getSelectedZoom();
156e5c31af7Sopenharmony_ci                img.style.width = (img.naturalWidth * factor) + "px";
157e5c31af7Sopenharmony_ci                img.style.height = (img.naturalHeight * factor) + "px";
158e5c31af7Sopenharmony_ci            }
159e5c31af7Sopenharmony_ci
160e5c31af7Sopenharmony_ci            // Rescales all <img> elements in the page. Used after changing the selected zoom.
161e5c31af7Sopenharmony_ci            var rescaleImages = function () {
162e5c31af7Sopenharmony_ci                var imageList = document.getElementsByTagName("img");
163e5c31af7Sopenharmony_ci                for (var i = 0; i < imageList.length; i++) {
164e5c31af7Sopenharmony_ci                    scaleSingleImage(imageList[i])
165e5c31af7Sopenharmony_ci                }
166e5c31af7Sopenharmony_ci            }
167e5c31af7Sopenharmony_ci
168e5c31af7Sopenharmony_ci            // Removes everything contained in the images <div>.
169e5c31af7Sopenharmony_ci            var clearImages = function () {
170e5c31af7Sopenharmony_ci                var imagesNode = document.getElementById("images");
171e5c31af7Sopenharmony_ci                while (imagesNode.hasChildNodes()) {
172e5c31af7Sopenharmony_ci                    imagesNode.removeChild(imagesNode.lastChild);
173e5c31af7Sopenharmony_ci                }
174e5c31af7Sopenharmony_ci            }
175e5c31af7Sopenharmony_ci
176e5c31af7Sopenharmony_ci            // Clears textarea text.
177e5c31af7Sopenharmony_ci            var clearText = function() {
178e5c31af7Sopenharmony_ci                document.getElementById("qpatext").value = "";
179e5c31af7Sopenharmony_ci            }
180e5c31af7Sopenharmony_ci
181e5c31af7Sopenharmony_ci            // Returns a properly sized image with the given base64-encoded PNG data.
182e5c31af7Sopenharmony_ci            var createImage = function (pngData, imageName) {
183e5c31af7Sopenharmony_ci                var imageContainer = document.createElement("figure");
184e5c31af7Sopenharmony_ci                if (imageName.length > 0) {
185e5c31af7Sopenharmony_ci                    var newFileNameHeader = document.createElement("figcaption");
186e5c31af7Sopenharmony_ci                    newFileNameHeader.textContent = escape(imageName);
187e5c31af7Sopenharmony_ci                    imageContainer.appendChild(newFileNameHeader);
188e5c31af7Sopenharmony_ci                }
189e5c31af7Sopenharmony_ci                var newImage = document.createElement("img");
190e5c31af7Sopenharmony_ci                newImage.src = "data:image/png;base64," + pngData;
191e5c31af7Sopenharmony_ci                newImage.onload = (function () {
192e5c31af7Sopenharmony_ci                    // Grab the image for the callback. We need to wait until
193e5c31af7Sopenharmony_ci                    // the image has been properly loaded to access its
194e5c31af7Sopenharmony_ci                    // naturalWidth and naturalHeight properties, needed for
195e5c31af7Sopenharmony_ci                    // scaling.
196e5c31af7Sopenharmony_ci                    var cbImage = newImage;
197e5c31af7Sopenharmony_ci                    return function () {
198e5c31af7Sopenharmony_ci                        scaleSingleImage(cbImage);
199e5c31af7Sopenharmony_ci                    };
200e5c31af7Sopenharmony_ci                })();
201e5c31af7Sopenharmony_ci                imageContainer.appendChild(newImage);
202e5c31af7Sopenharmony_ci                return imageContainer;
203e5c31af7Sopenharmony_ci            }
204e5c31af7Sopenharmony_ci
205e5c31af7Sopenharmony_ci            // Returns a new h3 header with the given file name.
206e5c31af7Sopenharmony_ci            var createFileNameHeader = function (fileName) {
207e5c31af7Sopenharmony_ci                var newHeader = document.createElement("h3");
208e5c31af7Sopenharmony_ci                newHeader.textContent = fileName;
209e5c31af7Sopenharmony_ci                return newHeader;
210e5c31af7Sopenharmony_ci            }
211e5c31af7Sopenharmony_ci
212e5c31af7Sopenharmony_ci            // Returns a new image block to contain images from a file.
213e5c31af7Sopenharmony_ci            var createImagesBlock = function () {
214e5c31af7Sopenharmony_ci                var imagesBlock = document.createElement("div");
215e5c31af7Sopenharmony_ci                imagesBlock.className = "imagesblock";
216e5c31af7Sopenharmony_ci                return imagesBlock;
217e5c31af7Sopenharmony_ci            }
218e5c31af7Sopenharmony_ci
219e5c31af7Sopenharmony_ci            // Processes a chunk of QPA text from the given file name. Creates
220e5c31af7Sopenharmony_ci            // the file name header and a list of images in the images <div>, as
221e5c31af7Sopenharmony_ci            // found in the text.
222e5c31af7Sopenharmony_ci            var processText = function(textString, fileName) {
223e5c31af7Sopenharmony_ci                var imagesNode = document.getElementById("images");
224e5c31af7Sopenharmony_ci                var newHeader = createFileNameHeader(fileName);
225e5c31af7Sopenharmony_ci                imagesNode.appendChild(newHeader);
226e5c31af7Sopenharmony_ci                var imagesBlock = createImagesBlock();
227e5c31af7Sopenharmony_ci                // [\s\S] is a match-anything regexp like the dot, except it
228e5c31af7Sopenharmony_ci                // also matches newlines. Ideally, browsers would need to widely
229e5c31af7Sopenharmony_ci                // support the "dotall" regexp modifier, but that's not the case
230e5c31af7Sopenharmony_ci                // yet and this does the trick.
231e5c31af7Sopenharmony_ci                // Group 1 are the image element properties, if any.
232e5c31af7Sopenharmony_ci                // Group 2 is the base64 PNG data.
233e5c31af7Sopenharmony_ci                var imageRegexp = /<Image\b(.*?)>([\s\S]*?)<\/Image>/g;
234e5c31af7Sopenharmony_ci                var imageNameRegexp = /\bName="(.*?)"/;
235e5c31af7Sopenharmony_ci                var result;
236e5c31af7Sopenharmony_ci                var innerResult;
237e5c31af7Sopenharmony_ci                var imageName;
238e5c31af7Sopenharmony_ci                while ((result = imageRegexp.exec(textString)) !== null) {
239e5c31af7Sopenharmony_ci                    innerResult = result[1].match(imageNameRegexp);
240e5c31af7Sopenharmony_ci                    imageName = ((innerResult !== null) ? innerResult[1] : "");
241e5c31af7Sopenharmony_ci                    // Blanks need to be removed from the base64 string.
242e5c31af7Sopenharmony_ci                    var pngData = result[2].replace(/\s+/g, "");
243e5c31af7Sopenharmony_ci                    imagesBlock.appendChild(createImage(pngData, imageName));
244e5c31af7Sopenharmony_ci                }
245e5c31af7Sopenharmony_ci                imagesNode.appendChild(imagesBlock);
246e5c31af7Sopenharmony_ci            }
247e5c31af7Sopenharmony_ci
248e5c31af7Sopenharmony_ci            // Loads images from the text in the text area.
249e5c31af7Sopenharmony_ci            var loadImages = function () {
250e5c31af7Sopenharmony_ci                processText(document.getElementById("qpatext").value, "<Pasted Text>");
251e5c31af7Sopenharmony_ci            }
252e5c31af7Sopenharmony_ci
253e5c31af7Sopenharmony_ci            // Loads images from the files in the file selector.
254e5c31af7Sopenharmony_ci            var handleFileSelect = function (evt) {
255e5c31af7Sopenharmony_ci                var files = evt.target.files;
256e5c31af7Sopenharmony_ci                for (var i = 0; i < files.length; i++) {
257e5c31af7Sopenharmony_ci                    // Creates a reader per file.
258e5c31af7Sopenharmony_ci                    var reader = new FileReader();
259e5c31af7Sopenharmony_ci                    // Grab the needed objects to use them after the file has
260e5c31af7Sopenharmony_ci                    // been read, in order to process its contents and add
261e5c31af7Sopenharmony_ci                    // images, if found, in the images <div>.
262e5c31af7Sopenharmony_ci                    reader.onload = (function () {
263e5c31af7Sopenharmony_ci                        var cbFileName = files[i].name;
264e5c31af7Sopenharmony_ci                        var cbReader = reader;
265e5c31af7Sopenharmony_ci                        return function () {
266e5c31af7Sopenharmony_ci                            processText(cbReader.result, cbFileName);
267e5c31af7Sopenharmony_ci                        };
268e5c31af7Sopenharmony_ci                    })();
269e5c31af7Sopenharmony_ci                    // Reads file contents. This will trigger the event above.
270e5c31af7Sopenharmony_ci                    reader.readAsText(files[i]);
271e5c31af7Sopenharmony_ci                }
272e5c31af7Sopenharmony_ci            }
273e5c31af7Sopenharmony_ci
274e5c31af7Sopenharmony_ci            // File selector trick: click on the selector when clicking on the
275e5c31af7Sopenharmony_ci            // custom button.
276e5c31af7Sopenharmony_ci            var clickFileSelector = function () {
277e5c31af7Sopenharmony_ci                document.getElementById("fileselector").click();
278e5c31af7Sopenharmony_ci            }
279e5c31af7Sopenharmony_ci
280e5c31af7Sopenharmony_ci            // Clears selected files to be able to select them again if needed.
281e5c31af7Sopenharmony_ci            var clearSelectedFiles = function() {
282e5c31af7Sopenharmony_ci                document.getElementById("fileselector").value = "";
283e5c31af7Sopenharmony_ci            }
284e5c31af7Sopenharmony_ci
285e5c31af7Sopenharmony_ci            // Set event handlers for interactive elements in the page.
286e5c31af7Sopenharmony_ci            document.getElementById("fileselector").onclick = clearSelectedFiles;
287e5c31af7Sopenharmony_ci            document.getElementById("fileselector").addEventListener("change", handleFileSelect, false);
288e5c31af7Sopenharmony_ci            document.getElementById("fileselectorbutton").onclick = clickFileSelector;
289e5c31af7Sopenharmony_ci            document.getElementById("loadimagesbutton").onclick = loadImages;
290e5c31af7Sopenharmony_ci            document.getElementById("cleartextbutton").onclick = clearText;
291e5c31af7Sopenharmony_ci            document.getElementById("zoomselect").onchange = rescaleImages;
292e5c31af7Sopenharmony_ci            document.getElementById("clearimagesbutton").onclick = clearImages;
293e5c31af7Sopenharmony_ci        </script>
294e5c31af7Sopenharmony_ci    </body>
295e5c31af7Sopenharmony_ci</html>
296