mirror of https://github.com/SixLabors/ImageSharp
473 changed files with 3950 additions and 3854 deletions
@ -0,0 +1,11 @@ |
|||
<StyleCopSettings Version="105"> |
|||
<Analyzers> |
|||
<Analyzer AnalyzerId="StyleCop.CSharp.DocumentationRules"> |
|||
<AnalyzerSettings> |
|||
<StringProperty Name="CompanyName">James South</StringProperty> |
|||
<StringProperty Name="Copyright">Copyright (c) James South. |
|||
Licensed under the Apache License, Version 2.0.</StringProperty> |
|||
</AnalyzerSettings> |
|||
</Analyzer> |
|||
</Analyzers> |
|||
</StyleCopSettings> |
|||
@ -1,8 +1,8 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<packages> |
|||
<package id="Csharp-Sqlite" version="3.7.7.1" targetFramework="net40" /> |
|||
<package id="Microsoft.Bcl" version="1.0.19" targetFramework="net40" /> |
|||
<package id="Microsoft.Bcl" version="1.1.3" targetFramework="net40" /> |
|||
<package id="Microsoft.Bcl.Async" version="1.0.16" targetFramework="net40" /> |
|||
<package id="Microsoft.Bcl.Build" version="1.0.6" targetFramework="net40" /> |
|||
<package id="Microsoft.Bcl.Build" version="1.0.8" targetFramework="net40" /> |
|||
<package id="sqlite-net" version="1.0.7" targetFramework="net40" /> |
|||
</packages> |
|||
@ -0,0 +1,5 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<packages > |
|||
<package id="Csharp-Sqlite" version="3.7.7.1" targetFramework="net40" /> |
|||
<package id="sqlite-net" version="1.0.7" targetFramework="net40" /> |
|||
</packages> |
|||
@ -0,0 +1,836 @@ |
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
// <copyright file="ColorQuantizer.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// <summary>
|
|||
// The color quantizer.
|
|||
// </summary>
|
|||
// --------------------------------------------------------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Imaging |
|||
{ |
|||
#region Using
|
|||
using System; |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using System.Drawing; |
|||
using System.Drawing.Imaging; |
|||
using System.Runtime.InteropServices; |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// The color quantizer.
|
|||
/// </summary>
|
|||
[SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Reviewed. Suppression is OK here.")] |
|||
public static class ColorQuantizer |
|||
{ |
|||
#region Quantize methods
|
|||
/// <summary>The quantize.</summary>
|
|||
/// <param name="image">The image.</param>
|
|||
/// <param name="bitmapPixelFormat">The bitmap pixel format.</param>
|
|||
/// <returns>The quantized image with the recalculated color palette.</returns>
|
|||
public static Bitmap Quantize(Image image, PixelFormat bitmapPixelFormat) |
|||
{ |
|||
// Use dither by default
|
|||
return Quantize(image, bitmapPixelFormat, true); |
|||
} |
|||
|
|||
/// <summary>The quantize.</summary>
|
|||
/// <param name="image">The image.</param>
|
|||
/// <param name="pixelFormat">The pixel format.</param>
|
|||
/// <param name="useDither">The use dither.</param>
|
|||
/// <returns>The quantized image with the recalculated color palette.</returns>
|
|||
public static Bitmap Quantize(Image image, PixelFormat pixelFormat, bool useDither) |
|||
{ |
|||
Bitmap tryBitmap = image as Bitmap; |
|||
|
|||
if (tryBitmap != null && tryBitmap.PixelFormat == PixelFormat.Format32bppArgb) |
|||
{ |
|||
// The image passed to us is ALREADY a bitmap in the right format. No need to create
|
|||
// a copy and work from there.
|
|||
return DoQuantize(tryBitmap, pixelFormat, useDither); |
|||
} |
|||
|
|||
// We use these values a lot
|
|||
int width = image.Width; |
|||
int height = image.Height; |
|||
Rectangle sourceRect = Rectangle.FromLTRB(0, 0, width, height); |
|||
|
|||
// create a 24-bit rgb version of the source image
|
|||
using (Bitmap bitmapSource = new Bitmap(width, height, PixelFormat.Format32bppArgb)) |
|||
{ |
|||
using (Graphics grfx = Graphics.FromImage(bitmapSource)) |
|||
{ |
|||
grfx.DrawImage(image, sourceRect, 0, 0, width, height, GraphicsUnit.Pixel); |
|||
} |
|||
|
|||
return DoQuantize(bitmapSource, pixelFormat, useDither); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Does the quantize.
|
|||
/// </summary>
|
|||
/// <param name="bitmapSource">The bitmap source.</param>
|
|||
/// <param name="pixelFormat">The pixel format.</param>
|
|||
/// <param name="useDither">if set to <c>true</c> [use dither].</param>
|
|||
/// <returns>The quantized image with the recalculated color palette.</returns>
|
|||
private static Bitmap DoQuantize(Bitmap bitmapSource, PixelFormat pixelFormat, bool useDither) |
|||
{ |
|||
// We use these values a lot
|
|||
int width = bitmapSource.Width; |
|||
int height = bitmapSource.Height; |
|||
Rectangle sourceRect = Rectangle.FromLTRB(0, 0, width, height); |
|||
|
|||
Bitmap bitmapOptimized = null; |
|||
|
|||
try |
|||
{ |
|||
// Create a bitmap with the same dimensions and the desired format
|
|||
bitmapOptimized = new Bitmap(width, height, pixelFormat); |
|||
|
|||
// Lock the bits of the source image for reading.
|
|||
// we will need to write if we do the dither.
|
|||
BitmapData bitmapDataSource = bitmapSource.LockBits( |
|||
sourceRect, |
|||
ImageLockMode.ReadWrite, |
|||
PixelFormat.Format32bppArgb); |
|||
|
|||
try |
|||
{ |
|||
// Perform the first pass, which generates the octree data
|
|||
// Create an Octree
|
|||
Octree octree = new Octree(pixelFormat); |
|||
|
|||
// Stride might be negative, indicating inverted row order.
|
|||
// Allocate a managed buffer for the pixel data, and copy it from the unmanaged pointer.
|
|||
int strideSource = Math.Abs(bitmapDataSource.Stride); |
|||
byte[] sourceDataBuffer = new byte[strideSource * height]; |
|||
Marshal.Copy(bitmapDataSource.Scan0, sourceDataBuffer, 0, sourceDataBuffer.Length); |
|||
|
|||
// We could skip every other row and/or every other column when sampling the colors
|
|||
// of the source image, rather than hitting every other pixel. It doesn't seem to
|
|||
// degrade the resulting image too much. But it doesn't really help the performance
|
|||
// too much because the majority of the time seems to be spent in other places.
|
|||
|
|||
// For every row
|
|||
int rowStartSource = 0; |
|||
for (int ndxRow = 0; ndxRow < height; ndxRow += 1) |
|||
{ |
|||
// For each column
|
|||
for (int ndxCol = 0; ndxCol < width; ndxCol += 1) |
|||
{ |
|||
// Add the color (4 bytes per pixel - ARGB)
|
|||
Pixel pixel = GetSourcePixel(sourceDataBuffer, rowStartSource, ndxCol); |
|||
octree.AddColor(pixel); |
|||
} |
|||
|
|||
rowStartSource += strideSource; |
|||
} |
|||
|
|||
// Get the optimized colors
|
|||
Color[] colors = octree.GetPaletteColors(); |
|||
|
|||
// Set the palette from the octree
|
|||
ColorPalette palette = bitmapOptimized.Palette; |
|||
for (var ndx = 0; ndx < palette.Entries.Length; ++ndx) |
|||
{ |
|||
// Use the colors we calculated
|
|||
// for the rest, just set to transparent
|
|||
palette.Entries[ndx] = (ndx < colors.Length) |
|||
? colors[ndx] |
|||
: Color.Transparent; |
|||
} |
|||
|
|||
bitmapOptimized.Palette = palette; |
|||
|
|||
// Lock the bits of the optimized bitmap for writing.
|
|||
// we will also need to read if we are doing 1bpp or 4bpp
|
|||
BitmapData bitmapDataOutput = bitmapOptimized.LockBits(sourceRect, ImageLockMode.ReadWrite, pixelFormat); |
|||
try |
|||
{ |
|||
// Create a managed array for the destination bytes given the desired color depth
|
|||
// and marshal the unmanaged data to the managed array
|
|||
int strideOutput = Math.Abs(bitmapDataOutput.Stride); |
|||
byte[] bitmapOutputBuffer = new byte[strideOutput * height]; |
|||
|
|||
// For each source pixel, compute the appropriate color index
|
|||
rowStartSource = 0; |
|||
int rowStartOutput = 0; |
|||
|
|||
for (int ndxRow = 0; ndxRow < height; ++ndxRow) |
|||
{ |
|||
// For each column
|
|||
for (int ndxCol = 0; ndxCol < width; ++ndxCol) |
|||
{ |
|||
// Get the source color
|
|||
Pixel pixel = GetSourcePixel(sourceDataBuffer, rowStartSource, ndxCol); |
|||
|
|||
// Get the closest palette index
|
|||
int paletteIndex = octree.GetPaletteIndex(pixel); |
|||
|
|||
// If we want to dither and this isn't the transparent pixel
|
|||
if (useDither && pixel.Alpha != 0) |
|||
{ |
|||
// Calculate the error
|
|||
Color paletteColor = colors[paletteIndex]; |
|||
int deltaRed = pixel.Red - paletteColor.R; |
|||
int deltaGreen = pixel.Green - paletteColor.G; |
|||
int deltaBlue = pixel.Blue - paletteColor.B; |
|||
|
|||
// Propagate the dither error.
|
|||
// we'll use a standard Floyd-Steinberg matrix (1/16):
|
|||
// | 0 0 0 |
|
|||
// | 0 x 7 |
|
|||
// | 3 5 1 |
|
|||
|
|||
// Make sure we're not on the right-hand edge
|
|||
if (ndxCol + 1 < width) |
|||
{ |
|||
DitherSourcePixel(sourceDataBuffer, rowStartSource, ndxCol + 1, deltaRed, deltaGreen, deltaBlue, 7); |
|||
} |
|||
|
|||
// Make sure we're not already on the bottom row
|
|||
if (ndxRow + 1 < height) |
|||
{ |
|||
int nextRow = rowStartSource + strideSource; |
|||
|
|||
// Make sure we're not on the left-hand column
|
|||
if (ndxCol > 0) |
|||
{ |
|||
// Down one row, but back one pixel
|
|||
DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol - 1, deltaRed, deltaGreen, deltaBlue, 3); |
|||
} |
|||
|
|||
// pixel directly below us
|
|||
DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol, deltaRed, deltaGreen, deltaBlue, 5); |
|||
|
|||
// Make sure we're not on the right-hand column
|
|||
if (ndxCol + 1 < width) |
|||
{ |
|||
// Down one row, but right one pixel
|
|||
DitherSourcePixel(sourceDataBuffer, nextRow, ndxCol + 1, deltaRed, deltaGreen, deltaBlue, 1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Set the bitmap index based on the format
|
|||
switch (pixelFormat) |
|||
{ |
|||
case PixelFormat.Format8bppIndexed: |
|||
// Each byte is a palette index
|
|||
bitmapOutputBuffer[rowStartOutput + ndxCol] = (byte)paletteIndex; |
|||
break; |
|||
|
|||
case PixelFormat.Format4bppIndexed: |
|||
// Each byte contains two pixels
|
|||
bitmapOutputBuffer[rowStartOutput + (ndxCol >> 1)] |= ((ndxCol & 1) == 1) |
|||
? (byte)(paletteIndex & 0x0f) // lower nibble
|
|||
: (byte)(paletteIndex << 4); // upper nibble
|
|||
break; |
|||
|
|||
case PixelFormat.Format1bppIndexed: |
|||
// Each byte contains eight pixels
|
|||
if (paletteIndex != 0) |
|||
{ |
|||
bitmapOutputBuffer[rowStartOutput + (ndxCol >> 3)] |= (byte)(0x80 >> (ndxCol & 0x07)); |
|||
} |
|||
|
|||
break; |
|||
} |
|||
} |
|||
|
|||
rowStartSource += strideSource; |
|||
rowStartOutput += strideOutput; |
|||
} |
|||
|
|||
// Now copy the calculated pixel bytes from the managed array to the unmanaged bitmap
|
|||
Marshal.Copy(bitmapOutputBuffer, 0, bitmapDataOutput.Scan0, bitmapOutputBuffer.Length); |
|||
} |
|||
finally |
|||
{ |
|||
bitmapOptimized.UnlockBits(bitmapDataOutput); |
|||
bitmapDataOutput = null; |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
bitmapSource.UnlockBits(bitmapDataSource); |
|||
bitmapDataSource = null; |
|||
} |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
// If any exception is thrown, dispose of the bitmap object
|
|||
// we've been working on before we rethrow and bail
|
|||
if (bitmapOptimized != null) |
|||
{ |
|||
bitmapOptimized.Dispose(); |
|||
} |
|||
|
|||
throw; |
|||
} |
|||
|
|||
// Caller is responsible for disposing of this bitmap!
|
|||
return bitmapOptimized; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dithers the source pixel.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer.</param>
|
|||
/// <param name="rowStart">The row start.</param>
|
|||
/// <param name="col">The column.</param>
|
|||
/// <param name="deltaRed">The delta red.</param>
|
|||
/// <param name="deltaGreen">The delta green.</param>
|
|||
/// <param name="deltaBlue">The delta blue.</param>
|
|||
/// <param name="weight">The weight.</param>
|
|||
private static void DitherSourcePixel(byte[] buffer, int rowStart, int col, int deltaRed, int deltaGreen, int deltaBlue, int weight) |
|||
{ |
|||
int colorIndex = rowStart + (col * 4); |
|||
buffer[colorIndex + 2] = ChannelAdjustment(buffer[colorIndex + 2], (deltaRed * weight) >> 4); |
|||
buffer[colorIndex + 1] = ChannelAdjustment(buffer[colorIndex + 1], (deltaGreen * weight) >> 4); |
|||
buffer[colorIndex] = ChannelAdjustment(buffer[colorIndex], (deltaBlue * weight) >> 4); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the source pixel.
|
|||
/// </summary>
|
|||
/// <param name="buffer">The buffer.</param>
|
|||
/// <param name="rowStart">The row start.</param>
|
|||
/// <param name="col">The column.</param>
|
|||
/// <returns>The source pixel.</returns>
|
|||
private static Pixel GetSourcePixel(byte[] buffer, int rowStart, int col) |
|||
{ |
|||
int colorIndex = rowStart + (col * 4); |
|||
return new Pixel |
|||
{ |
|||
Alpha = buffer[colorIndex + 3], |
|||
Red = buffer[colorIndex + 2], |
|||
Green = buffer[colorIndex + 1], |
|||
Blue = buffer[colorIndex] |
|||
}; |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
/// <summary>Gets the channel adjustment.</summary>
|
|||
/// <param name="current">The current.</param>
|
|||
/// <param name="offset">The offset.</param>
|
|||
/// <returns>The channel adjustment.</returns>
|
|||
private static byte ChannelAdjustment(byte current, int offset) |
|||
{ |
|||
return (byte)Math.Min(255, Math.Max(0, current + offset)); |
|||
} |
|||
|
|||
#region Octree class
|
|||
|
|||
/// <summary>data structure for storing and reducing colors used in the source image</summary>
|
|||
private class Octree |
|||
{ |
|||
/// <summary>The m_max colors.</summary>
|
|||
private readonly int octreeMaxColors; |
|||
|
|||
/// <summary>The m_reducible nodes.</summary>
|
|||
private readonly OctreeNode[] octreeReducibleNodes; |
|||
|
|||
/// <summary>The m_color count.</summary>
|
|||
private int octreeColorCount; |
|||
|
|||
/// <summary>The m_has transparent.</summary>
|
|||
private bool octreeHasTransparent; |
|||
|
|||
/// <summary>The m_last argb.</summary>
|
|||
private int octreeLastArgb; |
|||
|
|||
/// <summary>The m_last node.</summary>
|
|||
private OctreeNode octreeLastNode; |
|||
|
|||
/// <summary>The m_palette.</summary>
|
|||
private Color[] octreePalette; |
|||
|
|||
/// <summary>The m_root.</summary>
|
|||
private OctreeNode octreeRoot; |
|||
|
|||
/// <summary>Initializes a new instance of the <see cref="Octree"/> class. Constructor</summary>
|
|||
/// <param name="pixelFormat">desired pixel format</param>
|
|||
internal Octree(PixelFormat pixelFormat) |
|||
{ |
|||
// figure out the maximum colors from the pixel format passed in
|
|||
switch (pixelFormat) |
|||
{ |
|||
case PixelFormat.Format1bppIndexed: |
|||
this.octreeMaxColors = 2; |
|||
break; |
|||
|
|||
case PixelFormat.Format4bppIndexed: |
|||
this.octreeMaxColors = 16; |
|||
break; |
|||
|
|||
case PixelFormat.Format8bppIndexed: |
|||
this.octreeMaxColors = 256; |
|||
break; |
|||
|
|||
default: |
|||
throw new ArgumentException("Invalid Pixel Format", "pixelFormat"); |
|||
} |
|||
|
|||
// we need a list for each level that may have reducible nodes.
|
|||
// since the last level (level 7) is only made up of leaf nodes,
|
|||
// we don't need an array entry for it.
|
|||
this.octreeReducibleNodes = new OctreeNode[7]; |
|||
|
|||
// add the initial level-0 root node
|
|||
this.octreeRoot = new OctreeNode(0, this); |
|||
} |
|||
|
|||
/// <summary>Add the given pixel color to the octree</summary>
|
|||
/// <param name="pixel">points to the pixel color we want to add</param>
|
|||
internal void AddColor(Pixel pixel) |
|||
{ |
|||
// If the A value is non-zero (ignore the transparent color)
|
|||
if (pixel.Alpha > 0) |
|||
{ |
|||
// If we have a previous node and this color is the same as the last...
|
|||
if (this.octreeLastNode != null && pixel.Argb == this.octreeLastArgb) |
|||
{ |
|||
// Just add this color to the same last node
|
|||
this.octreeLastNode.AddColor(pixel); |
|||
} |
|||
else |
|||
{ |
|||
// Just start at the root. If a new color is added,
|
|||
// add one to the count (otherwise 0).
|
|||
this.octreeColorCount += this.octreeRoot.AddColor(pixel) ? 1 : 0; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
// Flag that we have a transparent color.
|
|||
this.octreeHasTransparent = true; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Given a pixel color, return the index of the palette entry
|
|||
/// we want to use in the reduced image. If the color is not in the
|
|||
/// octree, OctreeNode.GetPaletteIndex will return a negative number.
|
|||
/// In that case, we will have to calculate the palette index the brute-force
|
|||
/// method by computing the least distance to each color in the palette array.
|
|||
/// </summary>
|
|||
/// <param name="pixel">pointer to the pixel color we want to look up</param>
|
|||
/// <returns>index of the palette entry we want to use for this color</returns>
|
|||
internal int GetPaletteIndex(Pixel pixel) |
|||
{ |
|||
int paletteIndex = 0; |
|||
|
|||
// transparent is always the first entry, so if this is transparent,
|
|||
// don't do anything.
|
|||
if (pixel.Alpha > 0) |
|||
{ |
|||
paletteIndex = this.octreeRoot.GetPaletteIndex(pixel); |
|||
|
|||
// returns -1 if this value isn't in the octree.
|
|||
if (paletteIndex < 0) |
|||
{ |
|||
// Use the brute-force method of calculating the closest color
|
|||
// in the palette to the one we want
|
|||
int minDistance = int.MaxValue; |
|||
for (int ndx = 0; ndx < this.octreePalette.Length; ++ndx) |
|||
{ |
|||
Color paletteColor = this.octreePalette[ndx]; |
|||
|
|||
// Calculate the delta for each channel
|
|||
int deltaRed = pixel.Red - paletteColor.R; |
|||
int deltaGreen = pixel.Green - paletteColor.G; |
|||
int deltaBlue = pixel.Blue - paletteColor.B; |
|||
|
|||
// Calculate the distance-squared by summing each channel's square
|
|||
int distance = (deltaRed * deltaRed) + (deltaGreen * deltaGreen) + (deltaBlue * deltaBlue); |
|||
if (distance < minDistance) |
|||
{ |
|||
minDistance = distance; |
|||
paletteIndex = ndx; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return paletteIndex; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Return a color palette for the computed octree.
|
|||
/// </summary>
|
|||
/// <returns>A color palette for the computed octree</returns>
|
|||
internal Color[] GetPaletteColors() |
|||
{ |
|||
// If we haven't already computed it, compute it now
|
|||
if (this.octreePalette == null) |
|||
{ |
|||
// Start at the second-to-last reducible level
|
|||
int reductionLevel = this.octreeReducibleNodes.Length - 1; |
|||
|
|||
// We want to subtract one from the target if we have a transparent
|
|||
// bit because we want to save room for that special color
|
|||
int targetCount = this.octreeMaxColors - (this.octreeHasTransparent ? 1 : 0); |
|||
|
|||
// While we still have more colors than the target...
|
|||
while (this.octreeColorCount > targetCount) |
|||
{ |
|||
// Find the first reducible node, starting with the last level
|
|||
// that can have reducible nodes
|
|||
while (reductionLevel > 0 && this.octreeReducibleNodes[reductionLevel] == null) |
|||
{ |
|||
--reductionLevel; |
|||
} |
|||
|
|||
if (this.octreeReducibleNodes[reductionLevel] == null) |
|||
{ |
|||
// Shouldn't get here
|
|||
break; |
|||
} |
|||
|
|||
// We should have a node now
|
|||
OctreeNode newLeaf = this.octreeReducibleNodes[reductionLevel]; |
|||
this.octreeReducibleNodes[reductionLevel] = newLeaf.NextReducibleNode; |
|||
this.octreeColorCount -= newLeaf.Reduce() - 1; |
|||
} |
|||
|
|||
if (reductionLevel == 0 && !this.octreeHasTransparent) |
|||
{ |
|||
// If this was the top-most level, we now only have a single color
|
|||
// representing the average. That's not what we want.
|
|||
// use just black and white
|
|||
this.octreePalette = new Color[2]; |
|||
this.octreePalette[0] = Color.Black; |
|||
this.octreePalette[1] = Color.White; |
|||
|
|||
// And empty the octree so it always picks the closer of the black and white entries
|
|||
this.octreeRoot = new OctreeNode(0, this); |
|||
} |
|||
else |
|||
{ |
|||
// Now walk the tree, adding all the remaining colors to the list
|
|||
int paletteIndex = 0; |
|||
this.octreePalette = new Color[this.octreeColorCount + (this.octreeHasTransparent ? 1 : 0)]; |
|||
|
|||
// Add the transparent color if we need it
|
|||
if (this.octreeHasTransparent) |
|||
{ |
|||
this.octreePalette[paletteIndex++] = Color.Transparent; |
|||
} |
|||
|
|||
// Have the nodes insert their leaf colors
|
|||
this.octreeRoot.AddColorsToPalette(this.octreePalette, ref paletteIndex); |
|||
} |
|||
} |
|||
|
|||
return this.octreePalette; |
|||
} |
|||
|
|||
/// <summary>set up the values we need to reuse the given pointer if the next color is argb</summary>
|
|||
/// <param name="node">last node to which we added a color</param>
|
|||
/// <param name="argb">last color we added</param>
|
|||
private void SetLastNode(OctreeNode node, int argb) |
|||
{ |
|||
this.octreeLastNode = node; |
|||
this.octreeLastArgb = argb; |
|||
} |
|||
|
|||
/// <summary>When a reducible node is added, this method is called to add it to the appropriate
|
|||
/// reducible node list (given its level)</summary>
|
|||
/// <param name="reducibleNode">node to add to a reducible list</param>
|
|||
private void AddReducibleNode(OctreeNode reducibleNode) |
|||
{ |
|||
// hook this node into the front of the list.
|
|||
// this means the last one added will be the first in the list.
|
|||
reducibleNode.NextReducibleNode = this.octreeReducibleNodes[reducibleNode.Level]; |
|||
this.octreeReducibleNodes[reducibleNode.Level] = reducibleNode; |
|||
} |
|||
|
|||
#region OctreeNode class
|
|||
|
|||
/// <summary>Node for an Octree structure</summary>
|
|||
private class OctreeNode |
|||
{ |
|||
/// <summary>The s_level masks.</summary>
|
|||
private static readonly byte[] NodeLevelMasks = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; |
|||
|
|||
/// <summary>The m_level.</summary>
|
|||
private readonly int nodeLevel; |
|||
|
|||
/// <summary>The m_octree.</summary>
|
|||
private readonly Octree nodeOctree; |
|||
|
|||
/// <summary>The m_blue sum.</summary>
|
|||
private int nodeBlueSum; |
|||
|
|||
/// <summary>The m_child nodes.</summary>
|
|||
private OctreeNode[] nodeChildNodes; |
|||
|
|||
/// <summary>The m_green sum.</summary>
|
|||
private int nodeGreenSum; |
|||
|
|||
/// <summary>The m_is leaf.</summary>
|
|||
private bool nodeIsLeaf; |
|||
|
|||
/// <summary>The m_palette index.</summary>
|
|||
private int nodePaletteIndex; |
|||
|
|||
/// <summary>
|
|||
/// The pixel count.Information we need to calculate the average color for a set of pixels
|
|||
/// </summary>
|
|||
private int nodePixelCount; |
|||
|
|||
/// <summary>The m_red sum.</summary>
|
|||
private int nodeRedSum; |
|||
|
|||
/// <summary>Initializes a new instance of the <see cref="OctreeNode"/> class. Constructor</summary>
|
|||
/// <param name="level">level for this node</param>
|
|||
/// <param name="octree">owning octree</param>
|
|||
internal OctreeNode(int level, Octree octree) |
|||
{ |
|||
this.nodeOctree = octree; |
|||
this.nodeLevel = level; |
|||
|
|||
// Since there are only eight levels, if we get to level 7
|
|||
// We automatically make this a leaf node
|
|||
this.nodeIsLeaf = level == 7; |
|||
|
|||
if (!this.nodeIsLeaf) |
|||
{ |
|||
// Create the child array
|
|||
this.nodeChildNodes = new OctreeNode[8]; |
|||
|
|||
// Add it to the tree's reducible node list
|
|||
this.nodeOctree.AddReducibleNode(this); |
|||
} |
|||
} |
|||
|
|||
/// <summary>Gets Level.</summary>
|
|||
internal int Level |
|||
{ |
|||
get { return this.nodeLevel; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets NextReducibleNode.
|
|||
/// Once we compute a palette, this will be set
|
|||
/// to the palette index associated with this leaf node.
|
|||
/// Nodes are arranged in linked lists of reducible nodes for a given level.
|
|||
/// this field and property is used to traverse that list.
|
|||
/// </summary>
|
|||
internal OctreeNode NextReducibleNode { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the average color for this node.
|
|||
/// </summary>
|
|||
private Color NodeColor |
|||
{ |
|||
get |
|||
{ |
|||
// Average color is the sum of each channel divided by the pixel count
|
|||
return Color.FromArgb( |
|||
this.nodeRedSum / this.nodePixelCount, |
|||
this.nodeGreenSum / this.nodePixelCount, |
|||
this.nodeBlueSum / this.nodePixelCount); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Add the given color to this node if it is a leaf, otherwise recurse
|
|||
/// down the appropriate child
|
|||
/// </summary>
|
|||
/// <param name="pixel">color to add</param>
|
|||
/// <returns>true if a new color was added to the octree</returns>
|
|||
internal bool AddColor(Pixel pixel) |
|||
{ |
|||
bool colorAdded; |
|||
if (this.nodeIsLeaf) |
|||
{ |
|||
// Increase the pixel count for this node, and if
|
|||
// the result is 1, then this is a new color
|
|||
colorAdded = ++this.nodePixelCount == 1; |
|||
|
|||
// Add the color to the running sum for this node
|
|||
this.nodeRedSum += pixel.Red; |
|||
this.nodeGreenSum += pixel.Green; |
|||
this.nodeBlueSum += pixel.Blue; |
|||
|
|||
// Set the last node so we can quickly process adjacent pixels
|
|||
// with the same color
|
|||
this.nodeOctree.SetLastNode(this, pixel.Argb); |
|||
} |
|||
else |
|||
{ |
|||
// Get the index at this level for the rgb values
|
|||
int childIndex = this.GetChildIndex(pixel); |
|||
|
|||
// If there is no child, add one now to the next level
|
|||
if (this.nodeChildNodes[childIndex] == null) |
|||
{ |
|||
this.nodeChildNodes[childIndex] = new OctreeNode(this.nodeLevel + 1, this.nodeOctree); |
|||
} |
|||
|
|||
// Recurse...
|
|||
colorAdded = this.nodeChildNodes[childIndex].AddColor(pixel); |
|||
} |
|||
|
|||
return colorAdded; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Given a source color, return the palette index to use for the reduced image.
|
|||
/// Returns -1 if the color is not represented in the octree (this happens if
|
|||
/// the color has been dithered into a new color that did not appear in the
|
|||
/// original image when the octree was formed in pass 1.
|
|||
/// </summary>
|
|||
/// <param name="pixel">source color to look up</param>
|
|||
/// <returns>palette index to use</returns>
|
|||
internal int GetPaletteIndex(Pixel pixel) |
|||
{ |
|||
int paletteIndex = -1; |
|||
if (this.nodeIsLeaf) |
|||
{ |
|||
// Use this leaf node's palette index
|
|||
paletteIndex = this.nodePaletteIndex; |
|||
} |
|||
else |
|||
{ |
|||
// Get the index at this level for the rgb values
|
|||
var childIndex = this.GetChildIndex(pixel); |
|||
if (this.nodeChildNodes[childIndex] != null) |
|||
{ |
|||
// Recurse...
|
|||
paletteIndex = this.nodeChildNodes[childIndex].GetPaletteIndex(pixel); |
|||
} |
|||
} |
|||
|
|||
return paletteIndex; |
|||
} |
|||
|
|||
/// <summary>Reduce this node by combining all child nodes</summary>
|
|||
/// <returns>number of nodes removed</returns>
|
|||
internal int Reduce() |
|||
{ |
|||
int numReduced = 0; |
|||
if (!this.nodeIsLeaf) |
|||
{ |
|||
// For each child
|
|||
foreach (OctreeNode node in this.nodeChildNodes) |
|||
{ |
|||
if (node != null) |
|||
{ |
|||
OctreeNode childNode = node; |
|||
|
|||
// add the pixel count from the child
|
|||
this.nodePixelCount += childNode.nodePixelCount; |
|||
|
|||
// add the running color sums from the child
|
|||
this.nodeRedSum += childNode.nodeRedSum; |
|||
this.nodeGreenSum += childNode.nodeGreenSum; |
|||
this.nodeBlueSum += childNode.nodeBlueSum; |
|||
|
|||
++numReduced; |
|||
} |
|||
} |
|||
|
|||
this.nodeChildNodes = null; |
|||
this.nodeIsLeaf = true; |
|||
} |
|||
|
|||
return numReduced; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// If this is a leaf node, add its color to the palette array at the given index
|
|||
/// and increment the index. If not a leaf, recurse the children nodes.
|
|||
/// </summary>
|
|||
/// <param name="colorArray">array of colors</param>
|
|||
/// <param name="paletteIndex">index of the next empty slot in the array</param>
|
|||
internal void AddColorsToPalette(Color[] colorArray, ref int paletteIndex) |
|||
{ |
|||
if (this.nodeIsLeaf) |
|||
{ |
|||
// Save our index and increment the running index
|
|||
this.nodePaletteIndex = paletteIndex++; |
|||
|
|||
// The color for this node is the average color, which is created by
|
|||
// dividing the running sums for each channel by the pixel count
|
|||
colorArray[this.nodePaletteIndex] = this.NodeColor; |
|||
} |
|||
else |
|||
{ |
|||
// Just run through all the non-null children and recurse
|
|||
foreach (OctreeNode node in this.nodeChildNodes) |
|||
{ |
|||
if (node != null) |
|||
{ |
|||
node.AddColorsToPalette(colorArray, ref paletteIndex); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Return the child index for a given color.
|
|||
/// Depends on which level this node is in.
|
|||
/// </summary>
|
|||
/// <param name="pixel">color pixel to compute</param>
|
|||
/// <returns>child index (0-7)</returns>
|
|||
private int GetChildIndex(Pixel pixel) |
|||
{ |
|||
// lvl: 0 1 2 3 4 5 6 7
|
|||
// bit: 7 6 5 4 3 2 1 0
|
|||
var shift = 7 - this.nodeLevel; |
|||
int mask = NodeLevelMasks[this.nodeLevel]; |
|||
return ((pixel.Red & mask) >> (shift - 2)) | |
|||
((pixel.Green & mask) >> (shift - 1)) | |
|||
((pixel.Blue & mask) >> shift); |
|||
} |
|||
} |
|||
#endregion
|
|||
} |
|||
#endregion
|
|||
|
|||
#region Pixel class for ARGB values
|
|||
/// <summary>
|
|||
/// Structure of a Format32bppArgb pixel in memory.
|
|||
/// </summary>
|
|||
private class Pixel |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the blue component of the pixel.
|
|||
/// </summary>
|
|||
public byte Blue { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the green component of the pixel.
|
|||
/// </summary>
|
|||
public byte Green { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the red component of the pixel.
|
|||
/// </summary>
|
|||
public byte Red { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the alpha component of the pixel.
|
|||
/// </summary>
|
|||
public byte Alpha { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the argb combination of the pixel.
|
|||
/// </summary>
|
|||
public int Argb |
|||
{ |
|||
get |
|||
{ |
|||
return this.Alpha << 24 | this.Red << 16 | this.Green << 8 | this.Blue; |
|||
} |
|||
} |
|||
} |
|||
#endregion
|
|||
} |
|||
} |
|||
@ -1,543 +0,0 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="OctreeQuantizer.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Imaging |
|||
{ |
|||
#region Using
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Drawing; |
|||
using System.Drawing.Imaging; |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// Encapsulates methods to calculate the colour palette if an image using an octree pattern.
|
|||
/// </summary>
|
|||
internal class OctreeQuantizer : Quantizer |
|||
{ |
|||
#region Fields
|
|||
/// <summary>
|
|||
/// Stores the tree.
|
|||
/// </summary>
|
|||
private readonly Octree octree; |
|||
|
|||
/// <summary>
|
|||
/// The maximum allowed color depth.
|
|||
/// </summary>
|
|||
private readonly int maxColors; |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="T:ImageProcessor.Imaging.OctreeQuantizer">OctreeQuantizer</see> class.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// The Octree quantizer is a two pass algorithm. The initial pass sets up the octree,
|
|||
/// the second pass quantizes a colour based on the nodes in the tree
|
|||
/// </remarks>
|
|||
/// <param name="maxColors">The maximum number of colours to return, maximum 255.</param>
|
|||
/// <param name="maxColorBits">The number of significant bits minimum 1, maximum 8.</param>
|
|||
public OctreeQuantizer(int maxColors, int maxColorBits) |
|||
: base(false) |
|||
{ |
|||
if (maxColors > 255) |
|||
{ |
|||
throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colours should be less than 256"); |
|||
} |
|||
|
|||
if ((maxColorBits < 1) | (maxColorBits > 8)) |
|||
{ |
|||
throw new ArgumentOutOfRangeException("maxColorBits", maxColorBits, "This should be between 1 and 8"); |
|||
} |
|||
|
|||
// Construct the octree
|
|||
this.octree = new Octree(maxColorBits); |
|||
this.maxColors = maxColors; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Process the pixel in the first pass of the algorithm.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The pixel to quantize</param>
|
|||
/// <remarks>
|
|||
/// This function need only be overridden if your quantize algorithm needs two passes,
|
|||
/// such as an Octree quantizer.
|
|||
/// </remarks>
|
|||
protected override void InitialQuantizePixel(Color32 pixel) |
|||
{ |
|||
// Add the colour to the octree
|
|||
this.octree.AddColor(pixel); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Override this to process the pixel in the second pass of the algorithm.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The pixel to quantize</param>
|
|||
/// <returns>The quantized value.</returns>
|
|||
protected override byte QuantizePixel(Color32 pixel) |
|||
{ |
|||
// The colour at [this.maxColors] is set to transparent
|
|||
byte paletteIndex; |
|||
|
|||
// Get the palette index if this non-transparent
|
|||
if (pixel.Alpha > 0) |
|||
{ |
|||
paletteIndex = (byte)this.octree.GetPaletteIndex(pixel); |
|||
} |
|||
else |
|||
{ |
|||
paletteIndex = (byte)this.maxColors; |
|||
} |
|||
|
|||
return paletteIndex; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieve the palette for the quantized image
|
|||
/// </summary>
|
|||
/// <param name="original">Any old palette, this is overwritten</param>
|
|||
/// <returns>The new colour palette</returns>
|
|||
protected override ColorPalette GetPalette(ColorPalette original) |
|||
{ |
|||
// First off convert the octree to this.maxColors colours
|
|||
ArrayList palette = this.octree.Palletize(this.maxColors - 1); |
|||
|
|||
// Then convert the palette based on those colours
|
|||
for (int index = 0; index < palette.Count; index++) |
|||
{ |
|||
original.Entries[index] = (Color)palette[index]; |
|||
} |
|||
|
|||
// Add the transparent colour
|
|||
original.Entries[this.maxColors] = Color.FromArgb(0, 0, 0, 0); |
|||
|
|||
return original; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Describes a tree data structure in which each internal node has exactly eight children.
|
|||
/// </summary>
|
|||
private class Octree |
|||
{ |
|||
#region Fields
|
|||
/// <summary>
|
|||
/// Mask used when getting the appropriate pixels for a given node
|
|||
/// </summary>
|
|||
private static readonly int[] mask = new int[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; |
|||
|
|||
/// <summary>
|
|||
/// The root of the octree
|
|||
/// </summary>
|
|||
private readonly OctreeNode root; |
|||
|
|||
/// <summary>
|
|||
/// Number of leaves in the tree
|
|||
/// </summary>
|
|||
private int leafCount; |
|||
|
|||
/// <summary>
|
|||
/// Array of reducible nodes
|
|||
/// </summary>
|
|||
private OctreeNode[] reducibleNodes; |
|||
|
|||
/// <summary>
|
|||
/// Maximum number of significant bits in the image
|
|||
/// </summary>
|
|||
private int maxColorBits; |
|||
|
|||
/// <summary>
|
|||
/// Store the last node quantized
|
|||
/// </summary>
|
|||
private OctreeNode previousNode; |
|||
|
|||
/// <summary>
|
|||
/// Cache the previous color quantized
|
|||
/// </summary>
|
|||
private int previousColor; |
|||
#endregion
|
|||
|
|||
#region Constructors
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="T:ImageProcessor.Imaging.OctreeQuantizer.Octree">Octree</see> class.
|
|||
/// </summary>
|
|||
/// <param name="maxBits">The maximum number of significant bits in the image</param>
|
|||
public Octree(int maxBits) |
|||
{ |
|||
this.maxColorBits = maxBits; |
|||
this.leafCount = 0; |
|||
this.reducibleNodes = new OctreeNode[9]; |
|||
this.root = new OctreeNode(0, this.maxColorBits, this); |
|||
this.previousColor = 0; |
|||
this.previousNode = null; |
|||
} |
|||
#endregion
|
|||
|
|||
#region Properties
|
|||
/// <summary>
|
|||
/// Gets or sets the number of leaves in the tree
|
|||
/// </summary>
|
|||
public int Leaves |
|||
{ |
|||
get { return this.leafCount; } |
|||
set { this.leafCount = value; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the array of reducible nodes
|
|||
/// </summary>
|
|||
protected OctreeNode[] ReducibleNodes |
|||
{ |
|||
get { return this.reducibleNodes; } |
|||
} |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// Add a given colour value to the octree
|
|||
/// </summary>
|
|||
/// <param name="pixel">
|
|||
/// The color value to add.
|
|||
/// </param>
|
|||
public void AddColor(Color32 pixel) |
|||
{ |
|||
// Check if this request is for the same colour as the last
|
|||
if (this.previousColor == pixel.ARGB) |
|||
{ |
|||
// If so, check if I have a previous node setup. This will only occur if the first colour in the image
|
|||
// happens to be black, with an alpha component of zero.
|
|||
if (null == this.previousNode) |
|||
{ |
|||
this.previousColor = pixel.ARGB; |
|||
this.root.AddColor(pixel, this.maxColorBits, 0, this); |
|||
} |
|||
else |
|||
{ |
|||
// Just update the previous node
|
|||
this.previousNode.Increment(pixel); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
this.previousColor = pixel.ARGB; |
|||
this.root.AddColor(pixel, this.maxColorBits, 0, this); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reduce the depth of the tree
|
|||
/// </summary>
|
|||
public void Reduce() |
|||
{ |
|||
// Find the deepest level containing at least one reducible node
|
|||
int index = this.maxColorBits - 1; |
|||
while ((index > 0) && (this.reducibleNodes[index] == null)) |
|||
{ |
|||
index--; |
|||
} |
|||
|
|||
// Reduce the node most recently added to the list at level 'index'
|
|||
OctreeNode node = this.reducibleNodes[index]; |
|||
this.reducibleNodes[index] = node.NextReducible; |
|||
|
|||
// Decrement the leaf count after reducing the node
|
|||
this.leafCount -= node.Reduce(); |
|||
|
|||
// And just in case I've reduced the last color to be added, and the next color to
|
|||
// be added is the same, invalidate the previousNode...
|
|||
this.previousNode = null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Convert the nodes in the octree to a palette with a maximum of colorCount colours
|
|||
/// </summary>
|
|||
/// <param name="colorCount">The maximum number of colours</param>
|
|||
/// <returns>An array list with the palletized colours</returns>
|
|||
public ArrayList Palletize(int colorCount) |
|||
{ |
|||
while (this.Leaves > colorCount) |
|||
{ |
|||
this.Reduce(); |
|||
} |
|||
|
|||
// Now palletize the nodes
|
|||
ArrayList palette = new ArrayList(this.Leaves); |
|||
int paletteIndex = 0; |
|||
this.root.ConstructPalette(palette, ref paletteIndex); |
|||
|
|||
// And return the palette
|
|||
return palette; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the palette index for the passed colour.
|
|||
/// </summary>
|
|||
/// <param name="pixel">
|
|||
/// The color to return the palette index for.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The palette index for the passed colour.
|
|||
/// </returns>
|
|||
public int GetPaletteIndex(Color32 pixel) |
|||
{ |
|||
return this.root.GetPaletteIndex(pixel, 0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Keep track of the previous node that was quantized
|
|||
/// </summary>
|
|||
/// <param name="node">The node last quantized</param>
|
|||
protected void TrackPrevious(OctreeNode node) |
|||
{ |
|||
this.previousNode = node; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Class which encapsulates each node in the tree
|
|||
/// </summary>
|
|||
protected class OctreeNode |
|||
{ |
|||
#region Fields
|
|||
/// <summary>
|
|||
/// Flag indicating that this is a leaf node
|
|||
/// </summary>
|
|||
private bool leaf; |
|||
|
|||
/// <summary>
|
|||
/// Number of pixels in this node
|
|||
/// </summary>
|
|||
private int pixelCount; |
|||
|
|||
/// <summary>
|
|||
/// Red component
|
|||
/// </summary>
|
|||
private int red; |
|||
|
|||
/// <summary>
|
|||
/// Green Component
|
|||
/// </summary>
|
|||
private int green; |
|||
|
|||
/// <summary>
|
|||
/// Blue component
|
|||
/// </summary>
|
|||
private int blue; |
|||
|
|||
/// <summary>
|
|||
/// Pointers to any child nodes
|
|||
/// </summary>
|
|||
private OctreeNode[] children; |
|||
|
|||
/// <summary>
|
|||
/// The index of this node in the palette
|
|||
/// </summary>
|
|||
private int paletteIndex; |
|||
#endregion
|
|||
|
|||
#region Constructors
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="OctreeNode"/> class.
|
|||
/// </summary>
|
|||
/// <param name="level">
|
|||
/// The level in the tree = 0 - 7
|
|||
/// </param>
|
|||
/// <param name="colorBits">
|
|||
/// The number of significant color bits in the image
|
|||
/// </param>
|
|||
/// <param name="octree">
|
|||
/// The tree to which this node belongs
|
|||
/// </param>
|
|||
public OctreeNode(int level, int colorBits, Octree octree) |
|||
{ |
|||
// Construct the new node
|
|||
this.leaf = level == colorBits; |
|||
|
|||
this.red = this.green = this.blue = 0; |
|||
this.pixelCount = 0; |
|||
|
|||
// If a leaf, increment the leaf count
|
|||
if (this.leaf) |
|||
{ |
|||
octree.Leaves++; |
|||
this.NextReducible = null; |
|||
this.children = null; |
|||
} |
|||
else |
|||
{ |
|||
// Otherwise add this to the reducible nodes
|
|||
this.NextReducible = octree.ReducibleNodes[level]; |
|||
octree.ReducibleNodes[level] = this; |
|||
this.children = new OctreeNode[8]; |
|||
} |
|||
} |
|||
#endregion
|
|||
|
|||
#region Properties
|
|||
|
|||
/// <summary>
|
|||
/// Gets or the next reducible node
|
|||
/// </summary>
|
|||
public OctreeNode NextReducible { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the child nodes
|
|||
/// </summary>
|
|||
private OctreeNode[] Children |
|||
{ |
|||
get { return this.children; } |
|||
} |
|||
#endregion
|
|||
|
|||
#region Methods
|
|||
/// <summary>
|
|||
/// Add a color into the tree
|
|||
/// </summary>
|
|||
/// <param name="pixel">The color</param>
|
|||
/// <param name="colorBits">The number of significant color bits</param>
|
|||
/// <param name="level">The level in the tree</param>
|
|||
/// <param name="octree">The tree to which this node belongs</param>
|
|||
public void AddColor(Color32 pixel, int colorBits, int level, Octree octree) |
|||
{ |
|||
// Update the color information if this is a leaf
|
|||
if (this.leaf) |
|||
{ |
|||
this.Increment(pixel); |
|||
|
|||
// Setup the previous node
|
|||
octree.TrackPrevious(this); |
|||
} |
|||
else |
|||
{ |
|||
// Go to the next level down in the tree
|
|||
int shift = 7 - level; |
|||
int index = ((pixel.Red & mask[level]) >> (shift - 2)) | |
|||
((pixel.Green & mask[level]) >> (shift - 1)) | |
|||
((pixel.Blue & mask[level]) >> shift); |
|||
|
|||
OctreeNode child = this.Children[index]; |
|||
|
|||
if (null == child) |
|||
{ |
|||
// Create a new child node & store in the array
|
|||
child = new OctreeNode(level + 1, colorBits, octree); |
|||
this.Children[index] = child; |
|||
} |
|||
|
|||
// Add the color to the child node
|
|||
child.AddColor(pixel, colorBits, level + 1, octree); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reduce this node by removing all of its children
|
|||
/// </summary>
|
|||
/// <returns>The number of leaves removed</returns>
|
|||
public int Reduce() |
|||
{ |
|||
this.red = this.green = this.blue = 0; |
|||
int childPosition = 0; |
|||
|
|||
// Loop through all children and add their information to this node
|
|||
for (int index = 0; index < 8; index++) |
|||
{ |
|||
if (null != this.Children[index]) |
|||
{ |
|||
this.red += this.Children[index].red; |
|||
this.green += this.Children[index].green; |
|||
this.blue += this.Children[index].blue; |
|||
this.pixelCount += this.Children[index].pixelCount; |
|||
++childPosition; |
|||
this.Children[index] = null; |
|||
} |
|||
} |
|||
|
|||
// Now change this to a leaf node
|
|||
this.leaf = true; |
|||
|
|||
// Return the number of nodes to decrement the leaf count by
|
|||
return childPosition - 1; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Traverse the tree, building up the color palette
|
|||
/// </summary>
|
|||
/// <param name="palette">The palette</param>
|
|||
/// <param name="currentPaletteIndex">The current palette index</param>
|
|||
public void ConstructPalette(ArrayList palette, ref int currentPaletteIndex) |
|||
{ |
|||
if (this.leaf) |
|||
{ |
|||
// Consume the next palette index
|
|||
this.paletteIndex = currentPaletteIndex++; |
|||
|
|||
// And set the color of the palette entry
|
|||
palette.Add(Color.FromArgb(this.red / this.pixelCount, this.green / this.pixelCount, this.blue / this.pixelCount)); |
|||
} |
|||
else |
|||
{ |
|||
// Loop through children looking for leaves
|
|||
for (int index = 0; index < 8; index++) |
|||
{ |
|||
if (null != this.children[index]) |
|||
{ |
|||
this.children[index].ConstructPalette(palette, ref currentPaletteIndex); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Return the palette index for the passed color.
|
|||
/// </summary>
|
|||
/// <param name="pixel">
|
|||
/// The pixel.
|
|||
/// </param>
|
|||
/// <param name="level">
|
|||
/// The level.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The palette index for the passed color.
|
|||
/// </returns>
|
|||
public int GetPaletteIndex(Color32 pixel, int level) |
|||
{ |
|||
int currentPaletteIndex = this.paletteIndex; |
|||
|
|||
if (!this.leaf) |
|||
{ |
|||
int shift = 7 - level; |
|||
int index = ((pixel.Red & mask[level]) >> (shift - 2)) | |
|||
((pixel.Green & mask[level]) >> (shift - 1)) | |
|||
((pixel.Blue & mask[level]) >> shift); |
|||
|
|||
if (null != this.children[index]) |
|||
{ |
|||
currentPaletteIndex = this.children[index].GetPaletteIndex(pixel, level + 1); |
|||
} |
|||
else |
|||
{ |
|||
throw new Exception("Didn't expect this!"); |
|||
} |
|||
} |
|||
|
|||
return currentPaletteIndex; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Increment the pixel count and add to the color information
|
|||
/// </summary>
|
|||
/// <param name="pixel">
|
|||
/// The pixel.
|
|||
/// </param>
|
|||
public void Increment(Color32 pixel) |
|||
{ |
|||
this.pixelCount++; |
|||
this.red += pixel.Red; |
|||
this.green += pixel.Green; |
|||
this.blue += pixel.Blue; |
|||
} |
|||
#endregion
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,315 +0,0 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="Quantizer.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Imaging |
|||
{ |
|||
#region Using
|
|||
using System; |
|||
using System.Drawing; |
|||
using System.Drawing.Imaging; |
|||
using System.Runtime.InteropServices; |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// Encapsulates methods to calculate the color palette of an image.
|
|||
/// </summary>
|
|||
internal abstract class Quantizer |
|||
{ |
|||
#region Fields
|
|||
/// <summary>
|
|||
/// The flag used to indicate whether a single pass or two passes are needed for quantization.
|
|||
/// </summary>
|
|||
private readonly bool singlePass; |
|||
|
|||
/// <summary>
|
|||
/// The size in bytes of the 32 bpp Colour structure.
|
|||
/// </summary>
|
|||
private readonly int pixelSize; |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="T:ImageProcessor.Imaging.Quantizer">Quantizer</see> class.
|
|||
/// </summary>
|
|||
/// <param name="singlePass">
|
|||
/// If set to <see langword="true"/>, then the quantizer will loop through the source pixels once;
|
|||
/// otherwise, <see langword="false"/>.
|
|||
/// </param>
|
|||
protected Quantizer(bool singlePass) |
|||
{ |
|||
this.singlePass = singlePass; |
|||
this.pixelSize = Marshal.SizeOf(typeof(Color32)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Quantizes the given <see cref="T:System.Drawing.Image">Image</see> and returns the resulting output
|
|||
/// <see cref="T:System.Drawing.Bitmap">Bitmap.</see>
|
|||
/// </summary>
|
|||
/// <param name="source">The image to quantize</param>
|
|||
/// <returns>
|
|||
/// A quantized <see cref="T:System.Drawing.Bitmap">Bitmap</see> version of the <see cref="T:System.Drawing.Image">Image</see>
|
|||
/// </returns>
|
|||
public Bitmap Quantize(Image source) |
|||
{ |
|||
// Get the size of the source image
|
|||
int height = source.Height; |
|||
int width = source.Width; |
|||
|
|||
// And construct a rectangle from these dimensions
|
|||
Rectangle bounds = new Rectangle(0, 0, width, height); |
|||
|
|||
// First off take a 32bpp copy of the image
|
|||
using (Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb)) |
|||
{ |
|||
Bitmap output = null; |
|||
|
|||
// Define a pointer to the bitmap data
|
|||
BitmapData sourceData = null; |
|||
try |
|||
{ |
|||
// And construct an 8bpp version
|
|||
output = new Bitmap(width, height, PixelFormat.Format8bppIndexed); |
|||
|
|||
// Now lock the bitmap into memory
|
|||
using (Graphics graphics = Graphics.FromImage(copy)) |
|||
{ |
|||
graphics.PageUnit = GraphicsUnit.Pixel; |
|||
|
|||
// Draw the source image onto the copy bitmap,
|
|||
// which will effect a widening as appropriate.
|
|||
graphics.DrawImage(source, bounds); |
|||
} |
|||
|
|||
// Get the source image bits and lock into memory
|
|||
sourceData = copy.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); |
|||
|
|||
// Call the FirstPass function if not a single pass algorithm.
|
|||
// For something like an octree quantizer, this will run through
|
|||
// all image pixels, build a data structure, and create a palette.
|
|||
if (!this.singlePass) |
|||
{ |
|||
this.FirstPass(sourceData, width, height); |
|||
} |
|||
|
|||
// Then set the colour palette on the output bitmap. I'm passing in the current palette
|
|||
// as there's no way to construct a new, empty palette.
|
|||
output.Palette = this.GetPalette(output.Palette); |
|||
|
|||
// Then call the second pass which actually does the conversion
|
|||
this.SecondPass(sourceData, output, width, height, bounds); |
|||
} |
|||
catch |
|||
{ |
|||
if (output != null) |
|||
{ |
|||
output.Dispose(); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
// Ensure that the bits are unlocked
|
|||
copy.UnlockBits(sourceData); |
|||
} |
|||
|
|||
// Last but not least, return the output bitmap
|
|||
return output; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Execute the first pass through the pixels in the image
|
|||
/// </summary>
|
|||
/// <param name="sourceData">The source data</param>
|
|||
/// <param name="width">The width in pixels of the image</param>
|
|||
/// <param name="height">The height in pixels of the image</param>
|
|||
protected virtual void FirstPass(BitmapData sourceData, int width, int height) |
|||
{ |
|||
// Define the source data pointers. The source row is a byte to
|
|||
// keep addition of the stride value easier (as this is in bytes)
|
|||
IntPtr sourceRow = sourceData.Scan0; |
|||
|
|||
// Loop through each row
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
// Set the source pixel to the first pixel in this row
|
|||
IntPtr sourcePixel = sourceRow; |
|||
|
|||
// And loop through each column
|
|||
for (int col = 0; col < width; col++) |
|||
{ |
|||
this.InitialQuantizePixel(new Color32(sourcePixel)); |
|||
sourcePixel = (IntPtr)((int)sourcePixel + this.pixelSize); |
|||
} |
|||
|
|||
// Now I have the pixel, call the FirstPassQuantize function.
|
|||
// Add the stride to the source row
|
|||
sourceRow = (IntPtr)((long)sourceRow + sourceData.Stride); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Execute a second pass through the bitmap
|
|||
/// </summary>
|
|||
/// <param name="sourceData">The source bitmap, locked into memory</param>
|
|||
/// <param name="output">The output bitmap</param>
|
|||
/// <param name="width">The width in pixels of the image</param>
|
|||
/// <param name="height">The height in pixels of the image</param>
|
|||
/// <param name="bounds">The bounding rectangle</param>
|
|||
protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds) |
|||
{ |
|||
BitmapData outputData = null; |
|||
|
|||
try |
|||
{ |
|||
// Lock the output bitmap into memory
|
|||
outputData = output.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); |
|||
|
|||
// Define the source data pointers. The source row is a byte to
|
|||
// keep addition of the stride value easier (as this is in bytes)
|
|||
IntPtr sourceRow = sourceData.Scan0; |
|||
IntPtr sourcePixel = sourceRow; |
|||
IntPtr previousPixel = sourcePixel; |
|||
|
|||
// Now define the destination data pointers
|
|||
IntPtr destinationRow = outputData.Scan0; |
|||
IntPtr destinationPixel = destinationRow; |
|||
|
|||
// And convert the first pixel, so that I have values going into the loop.
|
|||
byte pixelValue = this.QuantizePixel(new Color32(sourcePixel)); |
|||
|
|||
// Assign the value of the first pixel
|
|||
Marshal.WriteByte(destinationPixel, pixelValue); |
|||
|
|||
// Loop through each row
|
|||
for (int row = 0; row < height; row++) |
|||
{ |
|||
// Set the source pixel to the first pixel in this row
|
|||
sourcePixel = sourceRow; |
|||
|
|||
// And set the destination pixel pointer to the first pixel in the row
|
|||
destinationPixel = destinationRow; |
|||
|
|||
// Loop through each pixel on this scan line
|
|||
for (int col = 0; col < width; col++) |
|||
{ |
|||
// Check if this is the same as the last pixel. If so use that value
|
|||
// rather than calculating it again. This is an inexpensive optimisation.
|
|||
if (Marshal.ReadByte(previousPixel) != Marshal.ReadByte(sourcePixel)) |
|||
{ |
|||
// Quantize the pixel
|
|||
pixelValue = this.QuantizePixel(new Color32(sourcePixel)); |
|||
|
|||
// And setup the previous pointer
|
|||
previousPixel = sourcePixel; |
|||
} |
|||
|
|||
// And set the pixel in the output
|
|||
Marshal.WriteByte(destinationPixel, pixelValue); |
|||
|
|||
sourcePixel = (IntPtr)((long)sourcePixel + this.pixelSize); |
|||
destinationPixel = (IntPtr)((long)destinationPixel + 1); |
|||
} |
|||
|
|||
// Add the stride to the source row
|
|||
sourceRow = (IntPtr)((long)sourceRow + sourceData.Stride); |
|||
|
|||
// And to the destination row
|
|||
destinationRow = (IntPtr)((long)destinationRow + outputData.Stride); |
|||
} |
|||
} |
|||
finally |
|||
{ |
|||
// Ensure that I unlock the output bits
|
|||
output.UnlockBits(outputData); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Override this to process the pixel in the first pass of the algorithm
|
|||
/// </summary>
|
|||
/// <param name="pixel">The pixel to quantize</param>
|
|||
/// <remarks>
|
|||
/// This function need only be overridden if your quantize algorithm needs two passes,
|
|||
/// such as an Octree quantizer.
|
|||
/// </remarks>
|
|||
protected virtual void InitialQuantizePixel(Color32 pixel) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Override this to process the pixel in the second pass of the algorithm.
|
|||
/// </summary>
|
|||
/// <param name="pixel">The pixel to quantize</param>
|
|||
/// <returns>The quantized value.</returns>
|
|||
protected abstract byte QuantizePixel(Color32 pixel); |
|||
|
|||
/// <summary>
|
|||
/// Retrieve the palette for the quantized image
|
|||
/// </summary>
|
|||
/// <param name="original">Any old palette, this is overwritten</param>
|
|||
/// <returns>The new color palette</returns>
|
|||
protected abstract ColorPalette GetPalette(ColorPalette original); |
|||
|
|||
/// <summary>
|
|||
/// Structure that defines a 32 bit color
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This structure is used to read data from a 32 bits per pixel image
|
|||
/// in memory, and is ordered in this manner as this is the way that
|
|||
/// the data is laid out in memory
|
|||
/// </remarks>
|
|||
[StructLayout(LayoutKind.Explicit)] |
|||
public struct Color32 |
|||
{ |
|||
/// <summary>
|
|||
/// Holds the blue component of the color
|
|||
/// </summary>
|
|||
[FieldOffset(0)] |
|||
public byte Blue; |
|||
|
|||
/// <summary>
|
|||
/// Holds the green component of the color
|
|||
/// </summary>
|
|||
[FieldOffset(1)] |
|||
public byte Green; |
|||
|
|||
/// <summary>
|
|||
/// Holds the red component of the color
|
|||
/// </summary>
|
|||
[FieldOffset(2)] |
|||
public byte Red; |
|||
|
|||
/// <summary>
|
|||
/// Holds the alpha component of the color
|
|||
/// </summary>
|
|||
[FieldOffset(3)] |
|||
public byte Alpha; |
|||
|
|||
/// <summary>
|
|||
/// Permits the color32 to be treated as a 32 bit integer.
|
|||
/// </summary>
|
|||
[FieldOffset(0)] |
|||
public int ARGB; |
|||
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="T:ImageProcessor.Imaging.Quantizer.Color32">Color32</see> structure.
|
|||
/// </summary>
|
|||
/// <param name="sourcePixel">The pointer to the pixel.</param>
|
|||
public Color32(IntPtr sourcePixel) |
|||
{ |
|||
this = (Color32)Marshal.PtrToStructure(sourcePixel, typeof(Color32)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the color for this Color32 object
|
|||
/// </summary>
|
|||
public Color Color |
|||
{ |
|||
get { return Color.FromArgb(this.Alpha, this.Red, this.Green, this.Blue); } |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
<StyleCopSettings Version="105"> |
|||
<Analyzers> |
|||
<Analyzer AnalyzerId="StyleCop.CSharp.DocumentationRules"> |
|||
<AnalyzerSettings> |
|||
<StringProperty Name="CompanyName">James South</StringProperty> |
|||
<StringProperty Name="Copyright">Copyright (c) James South. |
|||
Licensed under the Apache License, Version 2.0.</StringProperty> |
|||
</AnalyzerSettings> |
|||
</Analyzer> |
|||
</Analyzers> |
|||
</StyleCopSettings> |
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@ |
|||
eaff612b7db9e40f185c91161fd9c977faec69bb |
|||
@ -1 +0,0 @@ |
|||
2af35ccdf0476cbe432b2440be45ffd0f6c414f4 |
|||
@ -1 +0,0 @@ |
|||
1ac41e14e3ae5f8ac9b06bcfbbacc6c4a9841863 |
|||
@ -1 +0,0 @@ |
|||
13403db94dce99fd4e73114b3334d4ef0c2b4ae5 |
|||
@ -1 +0,0 @@ |
|||
8a3fd4491298fec4626034f03e534caac7f22941 |
|||
@ -1,43 +0,0 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> |
|||
<metadata> |
|||
<id>ImageProcessor.Web</id> |
|||
<version>2.2.0.1</version> |
|||
<title>ImageProcessor.Web</title> |
|||
<authors>James South</authors> |
|||
<owners>James South</owners> |
|||
<projectUrl>http://jimbobsquarepants.github.com/ImageProcessor/</projectUrl> |
|||
<requireLicenseAcceptance>false</requireLicenseAcceptance> |
|||
<description>ImageProcessor.Web adds a configurable HttpModule to your website which allows on-the-fly processing of image files. The module also comes with a file and browser based cache that can handle up to 12,960,000 images increasing your processing output and saving precious server memory. |
|||
|
|||
Methods include; Resize, Rotate, Flip, Crop, Watermark, Filter, Saturation, Brightness, Contrast, Quality, Format, Vignette, and Transparency. |
|||
|
|||
This package also requires Microsoft.Bcl.Async -pre on .NET 4.0 which will be added in the background on install if applicable. |
|||
|
|||
If you use ImageProcessor please get in touch via my twitter @james_m_south |
|||
|
|||
|
|||
Feedback is always welcome.</description> |
|||
<summary>An extension to ImageProcessor that allows on-the-fly processing of image files in an ASP.NET website</summary> |
|||
<releaseNotes>Fixed cache bug which caused unneccessary processing of images. |
|||
|
|||
If upgrading from < 2.2.0.0 You will have to delete your cache if upgrading to this version as the database differs.</releaseNotes> |
|||
<copyright>James South</copyright> |
|||
<language>en-GB</language> |
|||
<tags>Image, Imaging, ASP, Performance, Processing, HttpModule, Cache, Resize, Rotate, Flip, Crop, Filter, Effects, Quality, Watermark, Alpha, Vignette, Saturation, Brightness, Contrast, Gif, Jpeg, Bitmap, Png, Fluent</tags> |
|||
<dependencies> |
|||
<dependency id="ImageProcessor" version="1.5.0.0" /> |
|||
<dependency id="Csharp-Sqlite" version="3.7.7.1" /> |
|||
</dependencies> |
|||
</metadata> |
|||
<files> |
|||
<file src="content\net40\web.config.transform" target="content\net40\web.config.transform" /> |
|||
<file src="content\net45\web.config.transform" target="content\net45\web.config.transform" /> |
|||
<file src="..\ImageProcessor.Web\bin\Release\ImageProcessor.Web.dll" target="lib\net40\ImageProcessor.Web.dll" /> |
|||
<file src="lib\net40\System.Runtime.dll" target="lib\net40\System.Runtime.dll" /> |
|||
<file src="lib\net40\System.Threading.Tasks.dll" target="lib\net40\System.Threading.Tasks.dll" /> |
|||
<file src="..\ImageProcessor.Web\bin\Release\ImageProcessor.Web.dll" target="lib\net45\ImageProcessor.Web.dll" /> |
|||
<file src="tools\net40\install.ps1" target="tools\net40\install.ps1" /> |
|||
<file src="tools\net45\install.ps1" target="tools\net45\install.ps1" /> |
|||
</files> |
|||
</package> |
|||
@ -1 +0,0 @@ |
|||
46b009d93ab9f1ea75f1ea1efb0073b3d369d3e5 |
|||
@ -1 +0,0 @@ |
|||
50dc5dc47c964ccc80bb8abb22650a579ae796c3 |
|||
@ -1 +0,0 @@ |
|||
9142f8cdad57d5c52d8721112c18a8d26c6f9817 |
|||
@ -1 +0,0 @@ |
|||
24114542de37d7b4463b56749e64e34b0d43a9cc |
|||
@ -1 +0,0 @@ |
|||
3de0aa82f042cfdda2764554fb34e686b401df85 |
|||
@ -0,0 +1 @@ |
|||
8b33cb0b4f13802b62d2511239e212680ad67158 |
|||
@ -1,3 +1,3 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:1080cb71a8fa7ab3cf762868662ad79b943acae142dd5ed4cf07c7bfca8b58d6 |
|||
size 4195 |
|||
oid sha256:ddf3ebd67df6ff5d23516a5214aedf3694a2b6b644a3401a20c16bd7d9e78bf5 |
|||
size 6651 |
|||
|
|||
@ -0,0 +1,37 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<DirectedGraph GraphDirection="LeftToRight" xmlns="http://schemas.microsoft.com/vs/2009/dgml"> |
|||
<Nodes> |
|||
<Node Id="ImageProcessor.Web_NET45" Label="ImageProcessor.Web_NET45" Category="Project" /> |
|||
<Node Id="Csharp-Sqlite 3.7.7.1" Label="Csharp-Sqlite 3.7.7.1" Category="Package" /> |
|||
<Node Id="sqlite-net 1.0.7" Label="sqlite-net 1.0.7" Category="Package" /> |
|||
<Node Id="ImageProcessor.Web" Label="ImageProcessor.Web" Category="Project" /> |
|||
<Node Id="Csharp-Sqlite 3.7.7.1" Label="Csharp-Sqlite 3.7.7.1" Category="Package" /> |
|||
<Node Id="Microsoft.Bcl 1.0.19" Label="Microsoft.Bcl 1.0.19" Category="Package" /> |
|||
<Node Id="Microsoft.Bcl.Async 1.0.16" Label="Microsoft.Bcl.Async 1.0.16" Category="Package" /> |
|||
<Node Id="Microsoft.Bcl.Build 1.0.6" Label="Microsoft.Bcl.Build 1.0.6" Category="Package" /> |
|||
<Node Id="sqlite-net 1.0.7" Label="sqlite-net 1.0.7" Category="Package" /> |
|||
</Nodes> |
|||
<Links> |
|||
<Link Source="ImageProcessor.Web_NET45" Target="Csharp-Sqlite 3.7.7.1" Category="Installed Package" /> |
|||
<Link Source="ImageProcessor.Web_NET45" Target="sqlite-net 1.0.7" Category="Installed Package" /> |
|||
<Link Source="Microsoft.Bcl 1.0.19" Target="Microsoft.Bcl.Build 1.0.6" Category="Package Dependency" /> |
|||
<Link Source="Microsoft.Bcl.Async 1.0.16" Target="Microsoft.Bcl 1.0.19" Category="Package Dependency" /> |
|||
<Link Source="ImageProcessor.Web" Target="Csharp-Sqlite 3.7.7.1" Category="Installed Package" /> |
|||
<Link Source="ImageProcessor.Web" Target="Microsoft.Bcl.Async 1.0.16" Category="Installed Package" /> |
|||
<Link Source="ImageProcessor.Web" Target="sqlite-net 1.0.7" Category="Installed Package" /> |
|||
</Links> |
|||
<Categories> |
|||
<Category Id="Project" /> |
|||
<Category Id="Package" /> |
|||
</Categories> |
|||
<Styles> |
|||
<Style TargetType="Node" GroupLabel="Project" ValueLabel="True"> |
|||
<Condition Expression="HasCategory('Project')" /> |
|||
<Setter Property="Background" Value="Blue" /> |
|||
</Style> |
|||
<Style TargetType="Link" GroupLabel="Package Dependency" ValueLabel="True"> |
|||
<Condition Expression="HasCategory('Package Dependency')" /> |
|||
<Setter Property="Background" Value="Yellow" /> |
|||
</Style> |
|||
</Styles> |
|||
</DirectedGraph> |
|||
@ -1,212 +0,0 @@ |
|||
/* ==|== Flexo 2.0.1 ============================================================= |
|||
Author: James South |
|||
twitter : http://twitter.com/James_M_South |
|||
github : https://github.com/JimBobSquarePants/Flexo |
|||
Copyright (c) James South. |
|||
Licensed under the Apache License v2.0. |
|||
============================================================================== */ |
|||
|
|||
/* ============================================================================= |
|||
Base |
|||
========================================================================== */ |
|||
html { |
|||
/*Use the iOS devices hardware accelerator to provide native scrolling*/ |
|||
-webkit-overflow-scrolling: touch; |
|||
/* Prevents iOS text size adjust after orientation change, without disabling user zoom. */ |
|||
-webkit-text-size-adjust: 100%; |
|||
-ms-text-size-adjust: 100%; |
|||
} |
|||
|
|||
html, body { |
|||
height: 100%; |
|||
margin: 0; |
|||
position: relative; |
|||
} |
|||
|
|||
.page { |
|||
min-height: 100%; |
|||
position: relative; |
|||
margin-bottom: -150px; |
|||
padding-bottom: 150px; |
|||
-webkit-box-sizing: border-box; |
|||
-moz-box-sizing: border-box; |
|||
-ms-box-sizing: border-box; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.page.no-box { |
|||
padding-bottom: 0; |
|||
} |
|||
|
|||
.page-push, .page-footer { |
|||
height: 150px; |
|||
} |
|||
|
|||
.page-footer { |
|||
margin: 0 auto; |
|||
position: relative; |
|||
z-index: 1; |
|||
} |
|||
|
|||
.container { |
|||
margin: 0 auto; |
|||
/* Manages width in a single place */ |
|||
width: 95%; |
|||
max-width: 1140px; |
|||
} |
|||
|
|||
/* Contains floats so all columns can float left*/ |
|||
.container:before, |
|||
.container:after, |
|||
.row:before, |
|||
.row:after { |
|||
content: ""; |
|||
display: table; |
|||
} |
|||
|
|||
.container:after, |
|||
.row:after { |
|||
clear: both; |
|||
} |
|||
|
|||
/* ============================================================================= |
|||
Grid |
|||
========================================================================== */ |
|||
|
|||
[class*="clmn"] { |
|||
display: block; |
|||
-webkit-box-sizing: border-box; |
|||
-moz-box-sizing: border-box; |
|||
-ms-box-sizing: border-box; |
|||
box-sizing: border-box; |
|||
min-height: 1px; |
|||
float: left; |
|||
} |
|||
|
|||
/* ==|== media queries =================================================== |
|||
Portrait phone viewport to Landscape phone < 767px |
|||
========================================================================== */ |
|||
@media screen and (max-width: 767px) { |
|||
body:not(.flexo-fixed) [class*="clmn"], |
|||
body:not(.flexo-fixed) [class*="offset"] { |
|||
float: none; |
|||
width: 100%; |
|||
margin-left: 0!important; |
|||
} |
|||
} |
|||
|
|||
/* ============================================================================= |
|||
Grid |
|||
========================================================================== */ |
|||
[class*="clmn"] + [class*="clmn"]:not([class*="offset"]) { |
|||
margin-left: 2%; |
|||
} |
|||
|
|||
/* Columns */ |
|||
|
|||
/* Full width calculated with margins */ |
|||
.clmn1 { |
|||
width: 100%; |
|||
} |
|||
|
|||
/* 2 column */ |
|||
.clmn2 { |
|||
width: 49%; |
|||
} |
|||
|
|||
/* 3 column */ |
|||
.clmn3 { |
|||
width: 32%; |
|||
} |
|||
|
|||
/* 4 column */ |
|||
.clmn4 { |
|||
width: 23.5%; |
|||
} |
|||
|
|||
/* 5 column */ |
|||
.clmn5 { |
|||
width: 18.4%; |
|||
} |
|||
|
|||
/* Fillers*/ |
|||
/* 2/3 column */ |
|||
.clmn2-3 { |
|||
width: 66%; |
|||
} |
|||
|
|||
/* 3/4 column */ |
|||
.clmn3-4 { |
|||
width: 74.5%; |
|||
} |
|||
|
|||
/* 2/5 column */ |
|||
.clmn2-5 { |
|||
width: 38.8%; |
|||
} |
|||
|
|||
/* 3/5 column */ |
|||
.clmn3-5 { |
|||
width: 59.2%; |
|||
} |
|||
|
|||
/* 4/5 column */ |
|||
.clmn4-5 { |
|||
width: 79.6%; |
|||
} |
|||
|
|||
/* Offsetting columns */ |
|||
|
|||
/*offset 1/2*/ |
|||
.offset2 { |
|||
margin-left: 51%; |
|||
} |
|||
|
|||
/*offset 1/3 */ |
|||
.offset3 { |
|||
margin-left: 34%; |
|||
} |
|||
|
|||
/*offset 1/4 */ |
|||
.offset4 { |
|||
margin-left: 25.5%; |
|||
} |
|||
|
|||
/*offset 1/5 */ |
|||
.offset5 { |
|||
margin-left: 20.4%; |
|||
} |
|||
|
|||
/* offset 2/3 */ |
|||
.offset2-3 { |
|||
margin-left: 68%; |
|||
} |
|||
|
|||
/* offset 3/4 */ |
|||
.offset3-4 { |
|||
margin-left: 76.5%; |
|||
} |
|||
|
|||
/* offset 2/5 */ |
|||
.offset2-5 { |
|||
margin-left: 40.8%; |
|||
} |
|||
|
|||
/* offset 3/5 */ |
|||
.offset3-5 { |
|||
margin-left: 61.2%; |
|||
} |
|||
|
|||
/* offset 4/5 */ |
|||
.offset4-5 { |
|||
margin-left: 81.6%; |
|||
} |
|||
|
|||
|
|||
/* ============================================================================= |
|||
Fixed Grid |
|||
========================================================================== */ |
|||
.flexo-fixed .container { |
|||
/* Manages width in a single place */ |
|||
width: 1140px; |
|||
} |
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,64 +0,0 @@ |
|||
body { |
|||
font-family: "Segoe UI",Tahoma,Arial,Verdana,Sans-Serif; |
|||
color: #333; |
|||
} |
|||
|
|||
h1, h2, h3 { |
|||
font-family: "Segoe UI Light", "Segoe UI",Tahoma,Arial,Verdana,Sans-Serif; |
|||
font-weight: 400; |
|||
} |
|||
|
|||
h1 { |
|||
margin-top: 0; |
|||
font-size: 3em; |
|||
} |
|||
|
|||
section section { |
|||
padding-bottom: 1em; |
|||
margin-bottom: 2em; |
|||
} |
|||
|
|||
section section:nth-child(2n) { |
|||
background-color: #f3f3f3; |
|||
} |
|||
|
|||
.no-bullets { |
|||
padding-left: 0; |
|||
} |
|||
|
|||
.no-bullets > li { |
|||
list-style: none; |
|||
float: left; |
|||
margin-right: .5em; |
|||
} |
|||
|
|||
/* |
|||
* Clearfix: contain floats |
|||
* |
|||
* For modern browsers |
|||
* 1. The space content is one way to avoid an Opera bug when the |
|||
* `contenteditable` attribute is included anywhere else in the document. |
|||
* Otherwise it causes space to appear at the top and bottom of elements |
|||
* that receive the `clearfix` class. |
|||
* 2. The use of `table` rather than `block` is only necessary if using |
|||
* `:before` to contain the top-margins of child elements. |
|||
*/ |
|||
|
|||
.clearfix:before, |
|||
.clearfix:after { |
|||
content: " "; /* 1 */ |
|||
display: table; /* 2 */ |
|||
} |
|||
|
|||
.clearfix:after { |
|||
clear: both; |
|||
} |
|||
|
|||
/* |
|||
* For IE 6/7 only |
|||
* Include this rule to trigger hasLayout and contain floats. |
|||
*/ |
|||
|
|||
.clearfix { |
|||
*zoom: 1; |
|||
} |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:ea75dd804dbc2685d2189f9e5fba063427416cc3c024f9962ab10b8efc1471b0 |
|||
size 17302 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:bc4e1cd326a27789dfa3ad493b25611f31133f733e193e3eb5b4f8bc14429d1f |
|||
size 16510 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:2633b42dfcd1b253ae2733d854a8801bd8ed547157dbcb148c529a3f2e213298 |
|||
size 25044 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:38e73ab2ad96ab405755b53559490954f5eb6fb2bedffa2b619cfd5369f66998 |
|||
size 28894 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:319b3e4178951cabacd28dfee025f076e69e2c789d8a193236912a907083826c |
|||
size 27223 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:5b203a58a268c47fce91ef419f996ef175d5a87f967ac2cc7978fb07bb7906da |
|||
size 39920 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:0b50ba1e1003fcd44917f594081a3d4007ccbb65a2bd51e78de89c3ed413f301 |
|||
size 57565 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:24861d2f61cb23d5bd2ad01740a1086a0623bbc1b4ed35f04bda24b345b5c62a |
|||
size 28858 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:fca1fdc26920f19d9ee821975afd3640e25208993055e75d0118b29ba4c1f2d6 |
|||
size 41399 |
|||
@ -1,14 +0,0 @@ |
|||
@model List<string> |
|||
@{ |
|||
ViewBag.Title = "About Us"; |
|||
} |
|||
|
|||
<h2>About</h2> |
|||
<p> |
|||
@foreach(string image in Model) |
|||
{ |
|||
string path = image + "?width=150"; |
|||
|
|||
<img src="@path" alt="@image"/> |
|||
} |
|||
</p> |
|||
@ -1,20 +0,0 @@ |
|||
@model TimeSpan |
|||
@{ |
|||
Layout = null; |
|||
|
|||
double s = Model.TotalMilliseconds; |
|||
} |
|||
|
|||
<!DOCTYPE html> |
|||
|
|||
<html> |
|||
<head> |
|||
<title>@s</title> |
|||
</head> |
|||
<body> |
|||
<div> |
|||
Speed In Milliseconds: @s<br/> |
|||
Collision Rate: @ViewBag.Collision% |
|||
</div> |
|||
</body> |
|||
</html> |
|||
@ -1,18 +0,0 @@ |
|||
@{ |
|||
ViewBag.Title = "Responsive"; |
|||
} |
|||
<style type="text/css"> |
|||
img |
|||
{ |
|||
max-width: 100%; |
|||
} |
|||
</style> |
|||
<h2> |
|||
Responsive</h2> |
|||
<img src="/Images/desert.jpg?width=480" srcset="/Images/desert.jpg?width=768 480w, /Images/penguins.jpg?width=979 640w 2x, /Images/jellyfish.jpg?width=480 2x" |
|||
alt="desert" /> |
|||
@*<img src="/Images/desert.jpg?width=480" srcset="/Images/desert.jpg?width=768 480w, /Images/jellyfish.jpg?width=480 2x, /Images/penguins.jpg?width=979 640w 2x" |
|||
alt="desert" />*@ @*<img src="/Images/desert.jpg?width=480" srcset="/Images/desert.jpg?width=768 480w, /Images/desert.jpg?width=979 768w" |
|||
alt="desert" />*@ |
|||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> |
|||
<script src="/Scripts/img.srcsect.pollyfill.js" type="text/javascript"></script> |
|||
@ -1,5 +0,0 @@ |
|||
@using (Html.BeginForm("Upload", "Home", FormMethod.Post, new { enctype = "multipart/form-data" })) |
|||
{ |
|||
<input type="file" name="file" /> |
|||
<button type="submit">Upload</button> |
|||
} |
|||
@ -1,9 +0,0 @@ |
|||
@model System.Web.Mvc.HandleErrorInfo |
|||
|
|||
@{ |
|||
ViewBag.Title = "Error"; |
|||
} |
|||
|
|||
<h2> |
|||
Sorry, an error occurred while processing your request. |
|||
</h2> |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:03edc77f38fc0d2a46d1c2070dc6c883997988f6b7b9b85c9e3de1eb227f6110 |
|||
size 16995 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:b3d19c53b3b653a827dd6349cc7234ea79911cdab59c5eb7dea1390f2235cb6e |
|||
size 20043 |
|||
@ -1 +0,0 @@ |
|||
f4e00752418f01964b99ce300d96c16a9aa8d239 |
|||
@ -1 +0,0 @@ |
|||
fd46e83948190f508b8906f0822eca8dd4eda2ef |
|||
@ -1 +0,0 @@ |
|||
aa7c907774ef5492ae25ffb7e3ff9755257731bd |
|||
@ -1 +0,0 @@ |
|||
1038ebe89d7cfb28ec96a421867a2b0efc6dfdb4 |
|||
@ -1 +0,0 @@ |
|||
1456713687c96cd486c4ec8e57d3c4f6f6437f10 |
|||
@ -1 +0,0 @@ |
|||
4c16fd6b5b6b8cc426bc290d15ab41523be29403 |
|||
@ -1 +0,0 @@ |
|||
bbf8bc24452deb732adf66ef3488793859f4f5b7 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:61154029adbfafc4cdef890c8f38f90bdf89f28ee2282f2cdd06359566265e12 |
|||
size 16372 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:de30e8602ce43542a15d7e28820263cc6f7b23fef9860b877196ff8fe6b45d96 |
|||
size 25266 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:048c21b2d13aaad9d56e633f758cac27749fcbb50f23eeb3132493c05800b996 |
|||
size 20142 |
|||
@ -1,3 +0,0 @@ |
|||
version https://git-lfs.github.com/spec/v1 |
|||
oid sha256:9ffe500044c08bb10fa63d6610cfa08416de0b9edc528aae6827d2bb8fab5e8b |
|||
size 15088 |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue