|
|
Any idea how to add the functionality so i can save the image as transparent PNG? i am able to load the png and change the image by writing some text etc. No I would like to save the image as PNG so I can use it as tile in my WP7 Mango app.
Any ideas are appreciated.
|
|
Coordinator
Oct 1, 2011 at 6:21 AM
|
Such format functionality is not supported by this library. WBX is more about content creation.
The ImageTools lib might support PNG writing.
- Rene Schulte
|
|
|
|
yes, Imagetools lib is what I checked out, i wrote an extensionmethod (like WriteTga) for WritableBitmapEx which saves to PNG. (see below) based on that for the people who need it. Supports compression as well (make sure you add the SharpZipLib.WindowsPhone7)
using System;
using System.Globalization;
using System.IO;
using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
namespace System.Windows.Media.Imaging
{
static class PngChunkTypes
{
/// <summary>
/// The first chunk in a png file. Can only exists once. Contains
/// common information like the width and the height of the image or
/// the used compression method.
/// </summary>
public const string Header = "IHDR";
/// <summary>
/// The PLTE chunk contains from 1 to 256 palette entries, each a three byte
/// series in the RGB format.
/// </summary>
public const string Palette = "PLTE";
/// <summary>
/// The IDAT chunk contains the actual image data. The image can contains more
/// than one chunk of this type. All chunks together are the whole image.
/// </summary>
public const string Data = "IDAT";
/// <summary>
/// This chunk must appear last. It marks the end of the PNG datastream.
/// The chunk's data field is empty.
/// </summary>
public const string End = "IEND";
/// <summary>
/// This chunk specifies that the image uses simple transparency:
/// either alpha values associated with palette entries (for indexed-color images)
/// or a single transparent color (for grayscale and truecolor images).
/// </summary>
public const string PaletteAlpha = "tRNS";
/// <summary>
/// Textual information that the encoder wishes to record with the image can be stored in
/// tEXt chunks. Each tEXt chunk contains a keyword and a text string.
/// </summary>
public const string Text = "tEXt";
/// <summary>
/// This chunk specifies the relationship between the image samples and the desired
/// display output intensity.
/// </summary>
public const string Gamma = "gAMA";
/// <summary>
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
/// </summary>
public const string Physical = "pHYs";
}
sealed class PngHeader
{
/// <summary>
/// The dimension in x-direction of the image in pixels.
/// </summary>
public int Width;
/// <summary>
/// The dimension in y-direction of the image in pixels.
/// </summary>
public int Height;
/// <summary>
/// Bit depth is a single-byte integer giving the number of bits per sample
/// or per palette index (not per pixel). Valid values are 1, 2, 4, 8, and 16,
/// although not all values are allowed for all color types.
/// </summary>
public byte BitDepth;
/// <summary>
/// Color type is a integer that describes the interpretation of the
/// image data. Color type codes represent sums of the following values:
/// 1 (palette used), 2 (color used), and 4 (alpha channel used).
/// </summary>
public byte ColorType;
/// <summary>
/// Indicates the method used to compress the image data. At present,
/// only compression method 0 (deflate/inflate compression with a sliding
/// window of at most 32768 bytes) is defined.
/// </summary>
public byte CompressionMethod;
/// <summary>
/// Indicates the preprocessing method applied to the image
/// data before compression. At present, only filter method 0
/// (adaptive filtering with five basic filter types) is defined.
/// </summary>
public byte FilterMethod;
/// <summary>
/// Indicates the transmission order of the image data.
/// Two values are currently defined: 0 (no interlace) or 1 (Adam7 interlace).
/// </summary>
public byte InterlaceMethod;
}
public static partial class WriteableBitmapExtensions
{
private const int MaxBlockSize = 0xFFFF;
private static Stream _stream;
private static WriteableBitmap _image;
private const double DefaultDensityX = 75;
private const double DefaultDensityY = 75;
private static bool IsWritingUncompressed = false;
private static bool IsWritingGamma = true;
private static double Gamma = 2.2f;
public static void WritePNG(this WriteableBitmap bmp, System.IO.Stream destination)
{
Encode(bmp, destination);
}
public static void Encode(WriteableBitmap image, Stream stream)
{
_image = image;
_stream = stream;
// Write the png header.
stream.Write(
new byte[]
{
0x89, 0x50, 0x4E, 0x47,
0x0D, 0x0A, 0x1A, 0x0A
}, 0, 8);
PngHeader header = new PngHeader();
header.Width = image.PixelWidth;
header.Height = image.PixelHeight;
header.ColorType = 6;
header.BitDepth = 8;
header.FilterMethod = 0;
header.CompressionMethod = 0;
header.InterlaceMethod = 0;
WriteHeaderChunk(header);
WritePhysicsChunk();
WriteGammaChunk();
if (IsWritingUncompressed)
{
WriteDataChunksFast();
}
else
{
WriteDataChunks();
}
WriteEndChunk();
stream.Flush();
}
private static void WritePhysicsChunk()
{
int dpmX = (int)Math.Round(DefaultDensityX * 39.3700787d);
int dpmY = (int)Math.Round(DefaultDensityY * 39.3700787d);
byte[] chunkData = new byte[9];
WriteInteger(chunkData, 0, dpmX);
WriteInteger(chunkData, 4, dpmY);
chunkData[8] = 1;
WriteChunk(PngChunkTypes.Physical, chunkData);
}
private static void WriteGammaChunk()
{
if (IsWritingGamma)
{
int gammeValue = (int)(Gamma * 100000f);
byte[] fourByteData = new byte[4];
byte[] size = BitConverter.GetBytes(gammeValue);
fourByteData[0] = size[3]; fourByteData[1] = size[2]; fourByteData[2] = size[1]; fourByteData[3] = size[0];
WriteChunk(PngChunkTypes.Gamma, fourByteData);
}
}
private static void WriteDataChunksFast()
{
byte[] pixels = _image.ToByteArray();
// Convert the pixel array to a new array for adding
// the filter byte.
// --------------------------------------------------
byte[] data = new byte[_image.PixelWidth * _image.PixelHeight * 4 + _image.PixelHeight];
int rowLength = _image.PixelWidth * 4 + 1;
for (int y = 0; y < _image.PixelHeight; y++)
{
data[y * rowLength] = 0;
Array.Copy(pixels, y * _image.PixelWidth * 4, data, y * rowLength + 1, _image.PixelWidth * 4);
}
// --------------------------------------------------
Adler32 adler32 = new Adler32();
adler32.Update(data);
using (MemoryStream tempStream = new MemoryStream())
{
int remainder = data.Length;
int blockCount;
if ((data.Length % MaxBlockSize) == 0)
{
blockCount = data.Length / MaxBlockSize;
}
else
{
blockCount = (data.Length / MaxBlockSize) + 1;
}
// Write headers
tempStream.WriteByte(0x78);
tempStream.WriteByte(0xDA);
for (int i = 0; i < blockCount; i++)
{
// Write the length
ushort length = (ushort)((remainder < MaxBlockSize) ? remainder : MaxBlockSize);
if (length == remainder)
{
tempStream.WriteByte(0x01);
}
else
{
tempStream.WriteByte(0x00);
}
tempStream.Write(BitConverter.GetBytes(length), 0, 2);
// Write one's compliment of length
tempStream.Write(BitConverter.GetBytes((ushort)~length), 0, 2);
// Write blocks
tempStream.Write(data, (int)(i * MaxBlockSize), length);
// Next block
remainder -= MaxBlockSize;
}
WriteInteger(tempStream, (int)adler32.Value);
tempStream.Seek(0, SeekOrigin.Begin);
byte[] zipData = new byte[tempStream.Length];
tempStream.Read(zipData, 0, (int)tempStream.Length);
WriteChunk(PngChunkTypes.Data, zipData);
}
}
private static void WriteDataChunks()
{
byte[] pixels = _image.ToByteArray();
byte[] data = new byte[_image.PixelWidth * _image.PixelHeight * 4 + _image.PixelHeight];
int rowLength = _image.PixelWidth * 4 + 1;
for (int y = 0; y < _image.PixelHeight; y++)
{
byte compression = 0;
if (y > 0)
{
compression = 2;
}
data[y * rowLength] = compression;
for (int x = 0; x < _image.PixelWidth; x++)
{
// Calculate the offset for the new array.
int dataOffset = y * rowLength + x * 4 + 1;
// Calculate the offset for the original pixel array.
int pixelOffset = (y * _image.PixelWidth + x) * 4;
data[dataOffset + 0] = pixels[pixelOffset + 0];
data[dataOffset + 1] = pixels[pixelOffset + 1];
data[dataOffset + 2] = pixels[pixelOffset + 2];
data[dataOffset + 3] = pixels[pixelOffset + 3];
if (y > 0)
{
int lastOffset = ((y - 1) * _image.PixelWidth + x) * 4;
data[dataOffset + 0] -= pixels[lastOffset + 0];
data[dataOffset + 1] -= pixels[lastOffset + 1];
data[dataOffset + 2] -= pixels[lastOffset + 2];
data[dataOffset + 3] -= pixels[lastOffset + 3];
}
}
}
byte[] buffer = null;
int bufferLength = 0;
MemoryStream memoryStream = null;
try
{
memoryStream = new MemoryStream();
using (DeflaterOutputStream zStream = new DeflaterOutputStream(memoryStream))
{
zStream.Write(data, 0, data.Length);
zStream.Flush();
zStream.Finish();
bufferLength = (int)memoryStream.Length;
buffer = memoryStream.GetBuffer();
}
}
finally
{
if (memoryStream != null)
{
memoryStream.Dispose();
}
}
int numChunks = bufferLength / MaxBlockSize;
if (bufferLength % MaxBlockSize != 0)
{
numChunks++;
}
for (int i = 0; i < numChunks; i++)
{
int length = bufferLength - i * MaxBlockSize;
if (length > MaxBlockSize)
{
length = MaxBlockSize;
}
WriteChunk(PngChunkTypes.Data, buffer, i * MaxBlockSize, length);
}
}
private static void WriteEndChunk()
{
WriteChunk(PngChunkTypes.End, null);
}
private static void WriteHeaderChunk(PngHeader header)
{
byte[] chunkData = new byte[13];
WriteInteger(chunkData, 0, header.Width);
WriteInteger(chunkData, 4, header.Height);
chunkData[8] = header.BitDepth;
chunkData[9] = header.ColorType;
chunkData[10] = header.CompressionMethod;
chunkData[11] = header.FilterMethod;
chunkData[12] = header.InterlaceMethod;
WriteChunk(PngChunkTypes.Header, chunkData);
}
private static void WriteChunk(string type, byte[] data)
{
WriteChunk(type, data, 0, data != null ? data.Length : 0);
}
private static void WriteChunk(string type, byte[] data, int offset, int length)
{
WriteInteger(_stream, length);
byte[] typeArray = new byte[4];
typeArray[0] = (byte)type[0];
typeArray[1] = (byte)type[1];
typeArray[2] = (byte)type[2];
typeArray[3] = (byte)type[3];
_stream.Write(typeArray, 0, 4);
if (data != null)
{
_stream.Write(data, offset, length);
}
Crc32 crc32 = new Crc32();
crc32.Update(typeArray);
if (data != null)
{
crc32.Update(data, offset, length);
}
WriteInteger(_stream, (uint)crc32.Value);
}
private static void WriteInteger(byte[] data, int offset, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
Array.Copy(buffer, 0, data, offset, 4);
}
private static void WriteInteger(Stream stream, int value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, 4);
}
private static void WriteInteger(Stream stream, uint value)
{
byte[] buffer = BitConverter.GetBytes(value);
Array.Reverse(buffer);
stream.Write(buffer, 0, 4);
}
}
}
|
|
|
|
Matthijs, thanks for this extension method to save to PNG. However, I've noticed the images I am trying to convert change color when using it. They all come out with a blueish tint which may indicate some channel encoding error.
Are you by any chance aware of this issue ? I haven't had time to look into it just yet.
|
|
|
|
I used the code in this library to hack the extensionmethod together:
http://imagetools.codeplex.com/
|
|
|
|
I used code above and it works as advertised. I wonder if we can include it in the library itself, so i don't have to keep two separate
dll to save to
PNG.
i know i can get the source and put two together, its for the same of the users, that just want to use it out of the box :)
i use it with WP7.1 to create transparent tiles with background agent.
Thank you matthijshoekstra
|
|
Coordinator
Dec 8, 2011 at 7:52 AM
|
I talked with Matthijs about the integration of that PNG code. I want to keep the WriteableBitmpaEx library small and easy portable without any external dependencies. The PNG code above has a dependency on the SharpZipLib library that's why I won't include
it as it is. If the license of the other projects is OK, we can think about to extract the needed functionality. Contributions are always welcome. :)
- Rene Schulte
|
|
|
|
I had the same issue as guillembk with the color shift so I investigated and found Windows Phone returns the pixel in a different byte order than PNG expects. I've rewritten the code, removed the reliance on both WriteableBitmapEx and SharpZipLib
(though it obviously looses the compression of the PNG file) and rewitten most of the code to optimize the code down to a single copy of the image data in memory at any time and added a dedection function for the byte order being used by WirteableBitmap.
You can download the code here:
http://toolstack.com/libraries/pngwriter
|
|
|
|
I am going to store bytes from your Encode method as bytes not as png file. How do I decode them back into WriteableBitmap?
|
|
May 25, 2012 at 5:35 AM
Edited May 25, 2012 at 5:36 AM
|
have you tried using PngBitmapEncoder?
WriteableBitmap wb = new WriteableBitmap(image);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Interlace = PngInterlaceOption.Off;
encoder.Frames.Add(BitmapFrame.Create(wb));
using (FileStream fs = new FileStream(filename,FileMode.Create)) {
encoder.Save(fs);
fs.Close();
}
(assuming this exists on wp)
|
|
|
|
Thank you ! That what I was looking for. But I had to use something else to resolve the problem at the time.
|
|
|
|
JamiePate wrote:
have you tried using PngBitmapEncoder?
(assuming this exists on wp)
No it doesn't.
|
|
Oct 17, 2012 at 6:20 PM
Edited Oct 17, 2012 at 6:21 PM
|
Strange thing.
I have a module that creates a writablebitmap then does some text rendering:
void RenderText(ref WriteableBitmap wbm, int index, int ptIndex, VCI_Point_Data vf)
{
if (wbm.Pixels.Count() > 0)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = vf.Pt_Value.ToString();
wbm.Render(textBlock, new TranslateTransform() { Y = dyns[index].dyn_location.Y, X = dyns[index].dyn_location.X });
}
}
then does some flood filling:
// copied from http://forums.silverlight.net/t/177905.aspx/1
//
public static void SafeFloodFill( ref WriteableBitmap bm, int x, int y, int new_color,
ref int[] edges)
then renders the writeablebitmap back to a bitmapimage:
BitmapImage bi = new BitmapImage();
using (MemoryStream ms = new MemoryStream())
{
wbm.WritePNG(ms);
bi.SetSource(ms);
ms.Close();
}
d.BackgroundImage = bi;
/*
* ExtendedImage eim = wbm.ToImage();
Stream wbmms = eim.ToStreamByExtension(".png");
bi.SetSource(wbmms);
d.BackgroundImage = bi ;
* */
Note there are two ways shown above to convert the writeablebitmap back to a bitmapimage.
If I use the first approach, I get my floodfills but not my RenderTexts.
If I use the second approach, I get my RenderTexts but not my floodfills.
Any words of advice?
Boyd
|
|
|
|
Does anyone tried WritableBitmapEx with PNG file save in WinRT?
I download ICSharpCode source code and dig in, but Tga to Png conversion in WinRT looks so hard than I expected (especially DeflaterOutputStream in ICSharpCode)!!
|
|
Coordinator
Feb 14 at 4:45 PM
|
|
|
|
|
Rene, Thanks for your tip! :)
|
|
|
|
Hi,
Can you please advise how I should use the code, such that when I save a png file on a windows 7 machine, I can open the png file in any image editor on the machine. Currently I can save silverlight writeablebitmap in a png file, and see the image in NT explorer but not in other applications.
|
|