@ -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> |
|||
@ -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 |
|||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 6.5 KiB |
@ -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; |
|||
} |
|||
@ -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; |
|||
} |
|||
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 40 KiB |
@ -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> |
|||
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 20 KiB |
@ -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 |
|||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 15 KiB |