1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26// Bump this version when making changes that affect the storage format. 27const _imageStorageFormatVersion = 1; 28 29// Use as a default where an image version is not otherwise specified. 30// Bump the base version when making changes that affect the result image. 31const baseDefaultImageVersion = 4; 32const defaultImageVersion = baseDefaultImageVersion + 0.01 * WebInspector.Platform.version.base + 0.0001 * WebInspector.Platform.version.release; 33 34try { 35 var _generatedImageCacheDatabase = openDatabase("com.apple.WebInspector", 1, "Web Inspector Storage Database", 5 * 1024 * 1024); 36} catch (e) { 37 // If we can't open the database it isn't the end of the world, we just will always generate 38 // the images and not cache them for better load times. 39 console.warn("Can't open database due to: " + e + ". Images will be generated instead of loaded from cache."); 40} 41 42var _initialPrefetchComplete = false; 43var _fetchedCachedImages = {}; 44 45var _generatedImageUpdateFunctions = []; 46 47_prefetchCachedImagesAndUpdate(); 48 49// Updates each image when the device pixel ratio changes to redraw at the new resolution. 50window.matchMedia("(-webkit-device-pixel-ratio: 1)").addListener(_devicePixelRatioChanged); 51 52function _devicePixelRatioChanged() 53{ 54 _prefetchCachedImagesAndUpdate(); 55} 56 57function _registerGeneratedImageUpdateFunction(update) 58{ 59 console.assert(typeof update === "function"); 60 61 _generatedImageUpdateFunctions.push(update); 62 63 if (_initialPrefetchComplete) 64 update(); 65} 66 67function _logSQLError(tx, error) 68{ 69 console.error(error.code, error.message); 70} 71 72function _logSQLTransactionError(error) 73{ 74 console.error(error.code, error.message); 75} 76 77function _prefetchCachedImagesAndUpdate() 78{ 79 _fetchedCachedImages = {}; 80 81 function complete() 82 { 83 _initialPrefetchComplete = true; 84 85 for (var i = 0; i < _generatedImageUpdateFunctions.length; ++i) 86 _generatedImageUpdateFunctions[i](); 87 } 88 89 if (!_generatedImageCacheDatabase) { 90 complete(); 91 return; 92 } 93 94 _generatedImageCacheDatabase.transaction(function(tx) { 95 tx.executeSql("SELECT key, imageVersion, data FROM CachedImages WHERE pixelRatio = ? AND formatVersion = ?", [window.devicePixelRatio, _imageStorageFormatVersion], function(tx, result) { 96 for (var i = 0; i < result.rows.length; ++i) { 97 var row = result.rows.item(i); 98 _fetchedCachedImages[row.key] = {data: row.data, imageVersion: row.imageVersion}; 99 } 100 101 complete(); 102 }, function(tx, error) { 103 // The select failed. That could be because the schema changed or this is the first time. 104 // Drop the table and recreate it fresh. 105 106 tx.executeSql("DROP TABLE IF EXISTS CachedImages"); 107 tx.executeSql("CREATE TABLE CachedImages (key TEXT, pixelRatio INTEGER, formatVersion NUMERIC, imageVersion NUMERIC, data BLOB, UNIQUE(key, pixelRatio))", [], null, _logSQLError); 108 109 complete(); 110 }); 111 }, _logSQLTransactionError); 112} 113 114function platformImagePath(fileName) 115{ 116 if (WebInspector.Platform.isLegacyMacOS) 117 return "Images/Legacy/" + fileName; 118 return "Images/" + fileName; 119} 120 121function saveImageToStorage(storageKey, context, width, height, imageVersion) 122{ 123 console.assert(storageKey); 124 console.assert(context); 125 console.assert(typeof width === "number"); 126 console.assert(typeof height === "number"); 127 console.assert(typeof imageVersion === "number"); 128 129 if (!_generatedImageCacheDatabase) 130 return; 131 132 var imageData = context.getImageData(0, 0, width, height); 133 var imageDataPixels = new Uint32Array(imageData.data.buffer); 134 135 var imageDataString = ""; 136 for (var i = 0; i < imageDataPixels.length; ++i) 137 imageDataString += (i ? ":" : "") + (imageDataPixels[i] ? imageDataPixels[i].toString(36) : ""); 138 139 _generatedImageCacheDatabase.transaction(function(tx) { 140 tx.executeSql("INSERT OR REPLACE INTO CachedImages (key, pixelRatio, imageVersion, formatVersion, data) VALUES (?, ?, ?, ?, ?)", [storageKey, window.devicePixelRatio, imageVersion, _imageStorageFormatVersion, imageDataString], null, _logSQLError); 141 }, _logSQLTransactionError); 142} 143 144function restoreImageFromStorage(storageKey, context, width, height, imageVersion, generateCallback) 145{ 146 console.assert(storageKey); 147 console.assert(context); 148 console.assert(typeof width === "number"); 149 console.assert(typeof height === "number"); 150 console.assert(typeof imageVersion === "number"); 151 console.assert(typeof generateCallback === "function"); 152 153 if (!_generatedImageCacheDatabase) { 154 generateCallback(); 155 return; 156 } 157 158 var imageInfo = _fetchedCachedImages[storageKey]; 159 160 if (imageInfo) { 161 // We only want to keep the data around for the first use. These images 162 // are typically only used in one place. This keeps performance good 163 // during page load and frees memory that typically won't be reused. 164 delete _fetchedCachedImages[storageKey]; 165 } 166 167 if (imageInfo && (!imageInfo.data || imageInfo.imageVersion !== imageVersion)) { 168 generateCallback(); 169 return; 170 } 171 172 if (imageInfo) { 173 // Restore the image from the memory cache. 174 restoreImageData(imageInfo.data); 175 } else { 176 // Try fetching the image data from the database. 177 _generatedImageCacheDatabase.readTransaction(function(tx) { 178 tx.executeSql("SELECT data FROM CachedImages WHERE key = ? AND pixelRatio = ? AND imageVersion = ? AND formatVersion = ?", [storageKey, window.devicePixelRatio, imageVersion, _imageStorageFormatVersion], function(tx, result) { 179 if (!result.rows.length) { 180 generateCallback(); 181 return; 182 } 183 184 console.assert(result.rows.length === 1); 185 186 restoreImageData(result.rows.item(0).data); 187 }, function(tx, error) { 188 _logSQLError(tx, error); 189 190 generateCallback(); 191 }); 192 }, _logSQLTransactionError); 193 } 194 195 function restoreImageData(imageDataString) 196 { 197 var imageData = context.createImageData(width, height); 198 var imageDataPixels = new Uint32Array(imageData.data.buffer); 199 200 var imageDataArray = imageDataString.split(":"); 201 if (imageDataArray.length !== imageDataPixels.length) { 202 generateCallback(); 203 return; 204 } 205 206 for (var i = 0; i < imageDataArray.length; ++i) { 207 var pixelString = imageDataArray[i]; 208 imageDataPixels[i] = pixelString ? parseInt(pixelString, 36) : 0; 209 } 210 211 context.putImageData(imageData, 0, 0); 212 } 213} 214 215function generateColoredImage(inputImage, red, green, blue, alpha, width, height) 216{ 217 console.assert(inputImage); 218 219 if (alpha === undefined) 220 alpha = 1; 221 222 if (width === undefined) 223 width = inputImage.width; 224 225 if (height === undefined) 226 height = inputImage.height; 227 228 if (inputImage instanceof HTMLCanvasElement) { 229 // The input is already a canvas, so we can use its context directly. 230 var inputContext = inputImage.getContext("2d"); 231 } else { 232 console.assert(inputImage instanceof HTMLImageElement || inputImage instanceof HTMLVideoElement); 233 234 // The input is an image/video element, so we need to draw it into 235 // a canvas to get the pixel data. 236 var inputCanvas = document.createElement("canvas"); 237 inputCanvas.width = width; 238 inputCanvas.height = height; 239 240 var inputContext = inputCanvas.getContext("2d"); 241 inputContext.drawImage(inputImage, 0, 0, width, height); 242 } 243 244 var imageData = inputContext.getImageData(0, 0, width, height); 245 var imageDataPixels = new Uint32Array(imageData.data.buffer); 246 247 var isLittleEndian = Uint32Array.isLittleEndian(); 248 249 // Loop over the image data and set the color channels while preserving the alpha. 250 for (var i = 0; i < imageDataPixels.length; ++i) { 251 if (isLittleEndian) { 252 var existingAlpha = 0xff & (imageDataPixels[i] >> 24); 253 imageDataPixels[i] = red | green << 8 | blue << 16 | (existingAlpha * alpha) << 24; 254 } else { 255 var existingAlpha = 0xff & imageDataPixels[i]; 256 imageDataPixels[i] = red << 24 | green << 16 | blue << 8 | existingAlpha * alpha; 257 } 258 } 259 260 // Make a canvas that will be returned as the result. 261 var resultCanvas = document.createElement("canvas"); 262 resultCanvas.width = width; 263 resultCanvas.height = height; 264 265 var resultContext = resultCanvas.getContext("2d"); 266 267 resultContext.putImageData(imageData, 0, 0); 268 269 return resultCanvas; 270} 271 272function generateColoredImagesForCSS(imagePath, specifications, width, height, canvasIdentifierPrefix) 273{ 274 console.assert(imagePath); 275 console.assert(specifications); 276 console.assert(typeof width === "number"); 277 console.assert(typeof height === "number"); 278 279 var scaleFactor = window.devicePixelRatio; 280 var scaledWidth = width * scaleFactor; 281 var scaledHeight = height * scaleFactor; 282 283 canvasIdentifierPrefix = canvasIdentifierPrefix || ""; 284 285 const storageKeyPrefix = "generated-colored-image-"; 286 287 var imageElement = null; 288 var pendingImageLoadCallbacks = []; 289 290 _registerGeneratedImageUpdateFunction(update); 291 292 function imageLoaded() 293 { 294 console.assert(imageElement); 295 console.assert(imageElement.complete); 296 for (var i = 0; i < pendingImageLoadCallbacks.length; ++i) 297 pendingImageLoadCallbacks[i](); 298 pendingImageLoadCallbacks = null; 299 } 300 301 function ensureImageIsLoaded(callback) 302 { 303 if (imageElement && imageElement.complete) { 304 callback(); 305 return; 306 } 307 308 console.assert(pendingImageLoadCallbacks); 309 pendingImageLoadCallbacks.push(callback); 310 311 if (imageElement) 312 return; 313 314 imageElement = document.createElement("img"); 315 imageElement.addEventListener("load", imageLoaded); 316 imageElement.width = width; 317 imageElement.height = height; 318 imageElement.src = imagePath; 319 } 320 321 function restoreImages() 322 { 323 for (var canvasIdentifier in specifications) { 324 // Don't restore active images yet. 325 if (canvasIdentifier.indexOf("active") !== -1) 326 continue; 327 328 var specification = specifications[canvasIdentifier]; 329 restoreImage(canvasIdentifier, specification); 330 } 331 332 function restoreActiveImages() 333 { 334 for (var canvasIdentifier in specifications) { 335 // Only restore active images here. 336 if (canvasIdentifier.indexOf("active") === -1) 337 continue; 338 339 var specification = specifications[canvasIdentifier]; 340 restoreImage(canvasIdentifier, specification); 341 } 342 } 343 344 // Delay restoring the active states until later to improve the initial page load time. 345 setTimeout(restoreActiveImages, 500); 346 } 347 348 function restoreImage(canvasIdentifier, specification) 349 { 350 const storageKey = storageKeyPrefix + canvasIdentifierPrefix + canvasIdentifier; 351 const context = document.getCSSCanvasContext("2d", canvasIdentifierPrefix + canvasIdentifier, scaledWidth, scaledHeight); 352 restoreImageFromStorage(storageKey, context, scaledWidth, scaledHeight, specification.imageVersion || defaultImageVersion, function() { 353 ensureImageIsLoaded(generateImage.bind(null, canvasIdentifier, specification)); 354 }); 355 } 356 357 function update() 358 { 359 restoreImages(); 360 } 361 362 function generateImage(canvasIdentifier, specification) 363 { 364 console.assert(specification.fillColor instanceof Array); 365 console.assert(specification.fillColor.length === 3 || specification.fillColor.length === 4); 366 367 const context = document.getCSSCanvasContext("2d", canvasIdentifierPrefix + canvasIdentifier, scaledWidth, scaledHeight); 368 context.save(); 369 context.scale(scaleFactor, scaleFactor); 370 371 if (specification.shadowColor) { 372 context.shadowOffsetX = specification.shadowOffsetX || 0; 373 context.shadowOffsetY = specification.shadowOffsetY || 0; 374 context.shadowBlur = specification.shadowBlur || 0; 375 376 if (specification.shadowColor instanceof Array) { 377 if (specification.shadowColor.length === 3) 378 context.shadowColor = "rgb(" + specification.shadowColor.join(", ") + ")"; 379 else if (specification.shadowColor.length === 4) 380 context.shadowColor = "rgba(" + specification.shadowColor.join(", ") + ")"; 381 } else 382 context.shadowColor = specification.shadowColor; 383 } 384 385 var coloredImage = generateColoredImage(imageElement, specification.fillColor[0], specification.fillColor[1], specification.fillColor[2], specification.fillColor[3], scaledWidth, scaledHeight); 386 context.drawImage(coloredImage, 0, 0, width, height); 387 388 const storageKey = storageKeyPrefix + canvasIdentifierPrefix + canvasIdentifier; 389 saveImageToStorage(storageKey, context, scaledWidth, scaledHeight, specification.imageVersion || defaultImageVersion); 390 context.restore(); 391 } 392} 393 394function generateEmbossedImages(src, width, height, states, canvasIdentifierCallback, ignoreCache) 395{ 396 console.assert(src); 397 console.assert(typeof width === "number"); 398 console.assert(typeof height === "number"); 399 console.assert(states); 400 console.assert(states.Normal); 401 console.assert(states.Active); 402 console.assert(typeof canvasIdentifierCallback === "function"); 403 404 var scaleFactor = window.devicePixelRatio; 405 var scaledWidth = width * scaleFactor; 406 var scaledHeight = height * scaleFactor; 407 408 const imageVersion = defaultImageVersion; 409 410 const storageKeyPrefix = "generated-embossed-image-"; 411 412 var image = null; 413 var pendingImageLoadCallbacks = []; 414 415 _registerGeneratedImageUpdateFunction(update); 416 417 function imageLoaded() 418 { 419 console.assert(image); 420 console.assert(image.complete); 421 for (var i = 0; i < pendingImageLoadCallbacks.length; ++i) 422 pendingImageLoadCallbacks[i](); 423 pendingImageLoadCallbacks = null; 424 } 425 426 function ensureImageIsLoaded(callback) 427 { 428 if (image && image.complete) { 429 callback(); 430 return; 431 } 432 433 console.assert(pendingImageLoadCallbacks); 434 pendingImageLoadCallbacks.push(callback); 435 436 if (image) 437 return; 438 439 image = document.createElement("img"); 440 image.addEventListener("load", imageLoaded); 441 image.width = width; 442 image.height = height; 443 image.src = src; 444 } 445 446 function restoreImages() 447 { 448 restoreImage(states.Normal); 449 if (states.Focus) 450 restoreImage(states.Focus); 451 452 function restoreActiveImages() 453 { 454 restoreImage(states.Active); 455 if (states.ActiveFocus) 456 restoreImage(states.ActiveFocus); 457 } 458 459 // Delay restoring the active states until later to improve the initial page load time. 460 setTimeout(restoreActiveImages, 500); 461 } 462 463 function restoreImage(state) 464 { 465 const storageKey = storageKeyPrefix + canvasIdentifierCallback(state); 466 const context = document.getCSSCanvasContext("2d", canvasIdentifierCallback(state), scaledWidth, scaledHeight); 467 restoreImageFromStorage(storageKey, context, scaledWidth, scaledHeight, imageVersion, function() { 468 ensureImageIsLoaded(generateImage.bind(null, state)); 469 }); 470 } 471 472 function update() 473 { 474 if (ignoreCache) 475 generateImages(); 476 else 477 restoreImages(); 478 } 479 480 function generateImages() 481 { 482 ensureImageIsLoaded(generateImage.bind(null, states.Normal)); 483 484 if (states.Focus) 485 ensureImageIsLoaded(generateImage.bind(null, states.Focus)); 486 487 function generateActiveImages() 488 { 489 ensureImageIsLoaded(generateImage.bind(null, states.Active)); 490 491 if (states.ActiveFocus) 492 ensureImageIsLoaded(generateImage.bind(null, states.ActiveFocus)); 493 } 494 495 // Delay generating the active states until later to improve the initial page load time. 496 setTimeout(generateActiveImages, 500); 497 } 498 499 function generateImage(state) 500 { 501 function generateModernImage() 502 { 503 const context = document.getCSSCanvasContext("2d", canvasIdentifierCallback(state), scaledWidth, scaledHeight); 504 context.save(); 505 context.scale(scaleFactor, scaleFactor); 506 507 context.clearRect(0, 0, width, height); 508 509 var gradient = context.createLinearGradient(0, 0, 0, height); 510 if (state === states.Active) { 511 gradient.addColorStop(0, "rgb(65, 65, 65)"); 512 gradient.addColorStop(1, "rgb(70, 70, 70)"); 513 } else if (state === states.Focus) { 514 gradient.addColorStop(0, "rgb(0, 123, 247)"); 515 gradient.addColorStop(1, "rgb(0, 128, 252)"); 516 } else if (state === states.ActiveFocus) { 517 gradient.addColorStop(0, "rgb(0, 62, 210)"); 518 gradient.addColorStop(1, "rgb(0, 67, 215)"); 519 } else { 520 gradient.addColorStop(0, "rgb(75, 75, 75)"); 521 gradient.addColorStop(1, "rgb(80, 80, 80)"); 522 } 523 524 context.fillStyle = gradient; 525 context.fillRect(0, 0, width, height); 526 527 // Apply the mask to keep just the inner shape of the glyph. 528 _applyImageMask(context, image); 529 530 if (!ignoreCache) { 531 const storageKey = storageKeyPrefix + canvasIdentifierCallback(state); 532 saveImageToStorage(storageKey, context, scaledWidth, scaledHeight, imageVersion); 533 } 534 535 context.restore(); 536 } 537 538 function generateLegacyImage() 539 { 540 const depth = 1 * scaleFactor; 541 const shadowDepth = depth; 542 const shadowBlur = depth - 1; 543 const glowBlur = 2; 544 545 const context = document.getCSSCanvasContext("2d", canvasIdentifierCallback(state), scaledWidth, scaledHeight); 546 context.save(); 547 context.scale(scaleFactor, scaleFactor); 548 549 context.clearRect(0, 0, width, height); 550 551 if (depth > 0) { 552 // Use scratch canvas so we can apply the draw the white drop shadow 553 // to the whole glyph at the end. 554 555 var scratchCanvas = document.createElement("canvas"); 556 scratchCanvas.width = scaledWidth; 557 scratchCanvas.height = scaledHeight; 558 559 var scratchContext = scratchCanvas.getContext("2d"); 560 scratchContext.scale(scaleFactor, scaleFactor); 561 } else 562 var scratchContext = context; 563 564 var gradient = scratchContext.createLinearGradient(0, 0, 0, height); 565 if (state === states.Active) { 566 gradient.addColorStop(0, "rgb(60, 60, 60)"); 567 gradient.addColorStop(1, "rgb(100, 100, 100)"); 568 } else if (state === states.Focus) { 569 gradient.addColorStop(0, "rgb(50, 135, 200)"); 570 gradient.addColorStop(1, "rgb(60, 155, 225)"); 571 } else if (state === states.ActiveFocus) { 572 gradient.addColorStop(0, "rgb(30, 115, 185)"); 573 gradient.addColorStop(1, "rgb(40, 135, 200)"); 574 } else { 575 gradient.addColorStop(0, "rgb(90, 90, 90)"); 576 gradient.addColorStop(1, "rgb(145, 145, 145)"); 577 } 578 579 scratchContext.fillStyle = gradient; 580 scratchContext.fillRect(0, 0, width, height); 581 582 if (depth > 0) { 583 // Invert the image to use as a reverse image mask for the inner shadows. 584 // Pass in the color to use for the opaque areas to prevent "black halos" 585 // later when applying the final image mask. 586 587 if (state === states.Active) 588 var invertedImage = _invertMaskImage(image, 60, 60, 60); 589 else if (state === states.Focus) 590 var invertedImage = _invertMaskImage(image, 45, 145, 210); 591 else if (state === states.ActiveFocus) 592 var invertedImage = _invertMaskImage(image, 35, 125, 195); 593 else 594 var invertedImage = _invertMaskImage(image, 90, 90, 90); 595 596 if (state === states.Focus) { 597 // Double draw the blurry inner shadow to get the right effect. 598 _drawImageShadow(scratchContext, 0, 0, shadowDepth, "rgb(10, 95, 150)", invertedImage); 599 _drawImageShadow(scratchContext, 0, 0, shadowDepth, "rgb(10, 95, 150)", invertedImage); 600 601 // Draw the inner shadow. 602 _drawImageShadow(scratchContext, 0, shadowDepth, shadowBlur, "rgb(0, 80, 170)", invertedImage); 603 } else if (state === states.ActiveFocus) { 604 // Double draw the blurry inner shadow to get the right effect. 605 _drawImageShadow(scratchContext, 0, 0, shadowDepth, "rgb(0, 80, 100)", invertedImage); 606 _drawImageShadow(scratchContext, 0, 0, shadowDepth, "rgb(0, 80, 100)", invertedImage); 607 608 // Draw the inner shadow. 609 _drawImageShadow(scratchContext, 0, shadowDepth, shadowBlur, "rgb(0, 65, 150)", invertedImage); 610 } else { 611 // Double draw the blurry inner shadow to get the right effect. 612 _drawImageShadow(scratchContext, 0, 0, shadowDepth, "rgba(0, 0, 0, 1)", invertedImage); 613 _drawImageShadow(scratchContext, 0, 0, shadowDepth, "rgba(0, 0, 0, 1)", invertedImage); 614 615 // Draw the inner shadow. 616 _drawImageShadow(scratchContext, 0, shadowDepth, shadowBlur, "rgba(0, 0, 0, 0.6)", invertedImage); 617 } 618 } 619 620 // Apply the mask to keep just the inner shape of the glyph. 621 _applyImageMask(scratchContext, image); 622 623 // Draw the white drop shadow. 624 if (depth > 0) 625 _drawImageShadow(context, 0, shadowDepth, shadowBlur, "rgba(255, 255, 255, 0.6)", scratchCanvas); 626 627 // Draw a subtle glow for the focus states. 628 if (state === states.Focus || state === states.ActiveFocus) 629 _drawImageShadow(context, 0, 0, glowBlur, "rgba(20, 100, 220, 0.4)", scratchCanvas); 630 631 if (!ignoreCache) { 632 const storageKey = storageKeyPrefix + canvasIdentifierCallback(state); 633 saveImageToStorage(storageKey, context, scaledWidth, scaledHeight, imageVersion); 634 } 635 636 context.restore(); 637 } 638 639 if (WebInspector.Platform.isLegacyMacOS) 640 generateLegacyImage(); 641 else 642 generateModernImage(); 643 } 644 645 function _drawImageShadow(context, xOffset, yOffset, blur, color, image) { 646 context.save(); 647 648 context.shadowOffsetX = xOffset || 0; 649 context.shadowOffsetY = yOffset || 0; 650 context.shadowBlur = blur || 0; 651 context.shadowColor = color || "black"; 652 653 context.drawImage(image, 0, 0, width, height); 654 655 context.restore(); 656 } 657 658 function _invertMaskImage(image, red, green, blue) { 659 var bufferCanvas = document.createElement("canvas"); 660 bufferCanvas.width = scaledWidth; 661 bufferCanvas.height = scaledHeight; 662 663 var buffer = bufferCanvas.getContext("2d"); 664 buffer.scale(scaleFactor, scaleFactor); 665 buffer.drawImage(image, 0, 0, width, height); 666 667 var imageData = buffer.getImageData(0, 0, scaledWidth, scaledHeight); 668 var imageDataPixels = new Uint32Array(imageData.data.buffer); 669 670 red = red || 0; 671 green = green || 0; 672 blue = blue || 0; 673 674 var isLittleEndian = Uint32Array.isLittleEndian(); 675 676 for (var i = 0; i < imageDataPixels.length; ++i) { 677 if (isLittleEndian) { 678 var existingAlpha = 0xff & (imageDataPixels[i] >> 24); 679 imageDataPixels[i] = red | green << 8 | blue << 16 | (255 - existingAlpha) << 24; 680 } else { 681 var existingAlpha = 0xff & imageDataPixels[i]; 682 imageDataPixels[i] = red << 24 | green << 16 | blue << 8 | 255 - existingAlpha; 683 } 684 } 685 686 buffer.putImageData(imageData, 0, 0); 687 688 return bufferCanvas; 689 } 690 691 function _applyImageMask(context, image) { 692 var maskCanvas = document.createElement("canvas"); 693 maskCanvas.width = scaledWidth; 694 maskCanvas.height = scaledHeight; 695 696 var mask = maskCanvas.getContext("2d"); 697 mask.scale(scaleFactor, scaleFactor); 698 mask.drawImage(image, 0, 0, width, height); 699 700 var imageData = context.getImageData(0, 0, scaledWidth, scaledHeight); 701 var imageDataPixels = imageData.data; 702 703 var maskImageDataPixels = mask.getImageData(0, 0, scaledWidth, scaledHeight).data; 704 705 for (var i = 3; i < imageDataPixels.length; i += 4) 706 imageDataPixels[i] = maskImageDataPixels[i] * (imageDataPixels[i] / 255); 707 708 context.putImageData(imageData, 0, 0); 709 } 710} 711 712var svgImageCache = {}; 713 714function loadSVGImageDocumentElement(url, callback) 715{ 716 function invokeCallbackWithDocument(svgText) { 717 var parser = new DOMParser; 718 var doc = parser.parseFromString(svgText, "image/svg+xml"); 719 callback(doc.documentElement); 720 } 721 722 function imageLoad(event) { 723 if (xhr.status === 0 || xhr.status === 200) { 724 var svgText = xhr.responseText; 725 svgImageCache[url] = svgText; 726 invokeCallbackWithDocument(svgText); 727 } else { 728 console.error("Unexpected XHR status (" + xhr.status + ") loading SVG image: " + url); 729 callback(null); 730 } 731 } 732 733 function imageError(event) { 734 console.error("Unexpected failure loading SVG image: " + url); 735 callback(null); 736 } 737 738 var cachedSVGText = svgImageCache[url]; 739 if (cachedSVGText) { 740 invokeCallbackWithDocument(cachedSVGText); 741 return; 742 } 743 744 var xhr = new XMLHttpRequest; 745 xhr.open("GET", url, true); 746 xhr.addEventListener("load", imageLoad); 747 xhr.addEventListener("error", imageError); 748 xhr.send(); 749} 750 751function wrappedSVGDocument(url, className, title, callback) 752{ 753 loadSVGImageDocumentElement(url, function(svgDocument) { 754 if (!svgDocument) { 755 callback(null); 756 return; 757 } 758 759 var wrapper = document.createElement("div"); 760 if (className) 761 wrapper.className = className; 762 if (title) 763 wrapper.title = title; 764 wrapper.appendChild(svgDocument); 765 766 callback(wrapper); 767 }); 768} 769