Improved speed of decoding/encoding images, added generating animated previews, fixed drawing clock hands

fonts_experiment 1.0.2.0
Valeriy Mironov 2017-12-05 03:29:46 +02:00
parent 027b9c266d
commit 3d0b01fe0d
15 changed files with 190 additions and 104 deletions

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27004.2009
VisualStudioVersion = 15.0.27130.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WatchFace.Parser", "WatchFace.Parser\WatchFace.Parser.csproj", "{8D2E0224-DE7F-4EC9-AD23-9904B9D7B409}"
EndProject

View File

@ -1,4 +1,5 @@
using System.Drawing;
using BumpKit;
using NLog;
namespace Resources.Image
@ -10,45 +11,48 @@ namespace Resources.Image
public static void Process(Bitmap image)
{
var isImageAltered = false;
for (var y = 0; y < image.Height; y++)
for (var x = 0; x < image.Width; x++)
using (var context = image.CreateUnsafeContext())
{
var color = image.GetPixel(x, y);
var colorError = new ColorError(color);
if (colorError.IsZero) continue;
if (!isImageAltered)
for (var y = 0; y < context.Height; y++)
for (var x = 0; x < context.Width; x++)
{
Logger.Warn(
"Dithering applied for an image. Resource in watch face will use only supported colors. You can't get back original image by unpacking watch face."
);
isImageAltered = true;
}
var color = context.GetPixel(x, y);
var colorError = new ColorError(color);
image.SetPixel(x, y, colorError.NewColor);
if (colorError.IsZero) continue;
if (x + 1 < image.Width)
{
color = image.GetPixel(x + 1, y);
image.SetPixel(x + 1, y, colorError.ApplyError(color, 7, 16));
}
if (y + 1 < image.Height)
{
if (x > 1)
if (!isImageAltered)
{
color = image.GetPixel(x - 1, y + 1);
image.SetPixel(x - 1, y + 1, colorError.ApplyError(color, 3, 16));
Logger.Warn(
"Dithering applied for an image. Resource in watch face will use only supported colors. You can't get back original image by unpacking watch face."
);
isImageAltered = true;
}
color = image.GetPixel(x, y + 1);
image.SetPixel(x, y + 1, colorError.ApplyError(color, 5, 16));
context.SetPixel(x, y, colorError.NewColor);
if (x < image.Width - 1)
if (x + 1 < context.Width)
{
color = image.GetPixel(x + 1, y + 1);
image.SetPixel(x + 1, y + 1, colorError.ApplyError(color, 1, 16));
color = context.GetPixel(x + 1, y);
context.SetPixel(x + 1, y, colorError.ApplyError(color, 7, 16));
}
if (y + 1 < context.Height)
{
if (x > 1)
{
color = context.GetPixel(x - 1, y + 1);
context.SetPixel(x - 1, y + 1, colorError.ApplyError(color, 3, 16));
}
color = context.GetPixel(x, y + 1);
context.SetPixel(x, y + 1, colorError.ApplyError(color, 5, 16));
if (x < context.Width - 1)
{
color = context.GetPixel(x + 1, y + 1);
context.SetPixel(x + 1, y + 1, colorError.ApplyError(color, 1, 16));
}
}
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Drawing;
using System.IO;
using BumpKit;
using NLog;
using Resources.Utils;
@ -79,15 +80,18 @@ namespace Resources.Image
private Bitmap ReadImage()
{
var image = new Bitmap(_width, _height);
for (var y = 0; y < _height; y++)
using (var context = image.CreateUnsafeContext())
{
var rowBytes = _reader.ReadBytes(_rowLengthInBytes);
var bitReader = new BitReader(rowBytes);
for (var x = 0; x < _width; x++)
for (var y = 0; y < _height; y++)
{
var pixelColorIndex = bitReader.ReadBits(_bitsPerPixel);
var color = _palette[(int) pixelColorIndex];
image.SetPixel(x, y, color);
var rowBytes = _reader.ReadBytes(_rowLengthInBytes);
var bitReader = new BitReader(rowBytes);
for (var x = 0; x < _width; x++)
{
var pixelColorIndex = bitReader.ReadBits(_bitsPerPixel);
var color = _palette[(int) pixelColorIndex];
context.SetPixel(x, y, color);
}
}
}
return image;

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using BumpKit;
using NLog;
using Resources.Utils;
@ -70,26 +71,30 @@ namespace Resources.Image
private void ExtractPalette()
{
Logger.Trace("Extracting palette...");
for (var y = 0; y < _height; y++)
for (var x = 0; x < _width; x++)
{
var color = _image.GetPixel(x, y);
if (_palette.Contains(color)) continue;
if (color.A < 0x80 && _transparency == 0)
using (var context = _image.CreateUnsafeContext())
{
for (var y = 0; y < _height; y++)
for (var x = 0; x < _width; x++)
{
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}, Transaparent color",
_palette.Count, color.R, color.G, color.B
);
_palette.Insert(0, color);
_transparency = 1;
}
else
{
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}",
_palette.Count, color.R, color.G, color.B
);
_palette.Add(color);
var color = context.GetPixel(x, y);
if (_palette.Contains(color)) continue;
if (color.A < 0x80 && _transparency == 0)
{
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}, Transaparent color",
_palette.Count, color.R, color.G, color.B
);
_palette.Insert(0, color);
_transparency = 1;
}
else
{
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}",
_palette.Count, color.R, color.G, color.B
);
_palette.Add(color);
}
}
}
_paletteColors = (ushort) _palette.Count;
@ -136,26 +141,29 @@ namespace Resources.Image
i++;
}
for (var y = 0; y < _height; y++)
using (var context = _image.CreateUnsafeContext())
{
var rowData = new byte[_rowLengthInBytes];
var memoryStream = new MemoryStream(rowData);
var bitWriter = new BitWriter(memoryStream);
for (var x = 0; x < _width; x++)
for (var y = 0; y < _height; y++)
{
var color = _image.GetPixel(x, y);
if (color.A < 0x80 && _transparency == 1)
var rowData = new byte[_rowLengthInBytes];
var memoryStream = new MemoryStream(rowData);
var bitWriter = new BitWriter(memoryStream);
for (var x = 0; x < _width; x++)
{
bitWriter.WriteBits(0, _bitsPerPixel);
}
else
{
var paletteIndex = paletteHash[color];
bitWriter.WriteBits(paletteIndex, _bitsPerPixel);
var color = context.GetPixel(x, y);
if (color.A < 0x80 && _transparency == 1)
{
bitWriter.WriteBits(0, _bitsPerPixel);
}
else
{
var paletteIndex = paletteHash[color];
bitWriter.WriteBits(paletteIndex, _bitsPerPixel);
}
}
bitWriter.Flush();
_writer.Write(rowData);
}
bitWriter.Flush();
_writer.Write(rowData);
}
}
}

View File

@ -31,5 +31,4 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyVersion("1.0.*")]

View File

@ -31,6 +31,9 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="BumpKit, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Bumpkit.1.0.2\lib\BumpKit.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.12\lib\net40\NLog.dll</HintPath>
</Reference>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Bumpkit" version="1.0.2" targetFramework="net40-client" />
<package id="NLog" version="4.4.12" targetFramework="net40-client" />
</packages>

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using WatchFace.Parser.Interfaces;
@ -29,7 +30,7 @@ namespace WatchFace.Parser.Models.Elements.Common
}
else
{
drawer.FillPolygon(new SolidBrush(Color), points);
drawer.FillPolygon(new SolidBrush(Color), points, FillMode.Alternate);
drawer.DrawPolygon(new Pen(Color, 1), points);
}
@ -40,8 +41,8 @@ namespace WatchFace.Parser.Models.Elements.Common
{
var radians = degrees / 180 * Math.PI;
var x = element.X * Math.Cos(radians) + element.Y * Math.Sin(radians);
var y = element.X * Math.Sin(radians) + element.Y * Math.Cos(radians);
return new Point((int) Math.Round(x + Center.X), (int) Math.Round(y + Center.Y));
var y = element.X * Math.Sin(radians) - element.Y * Math.Cos(radians);
return new Point((int) Math.Floor(x + Center.X), (int) Math.Floor(y + Center.Y));
}
protected override Element CreateChildForParameter(Parameter parameter)

View File

@ -15,18 +15,19 @@ namespace WatchFace.Parser.Models.Elements
public void Draw(Graphics drawer, Bitmap[] resources, WatchState state)
{
if (state.CurrentWeather != WeatherCondition.Unknown)
if (state.CurrentWeather != WeatherCondition.Unknown && Current != null)
drawer.DrawImage(LoadWeatherImage(state.CurrentWeather), Current.X, Current.Y);
else if (state.TodayWeather != WeatherCondition.Unknown)
else if (state.TodayWeather != WeatherCondition.Unknown && Today != null)
drawer.DrawImage(LoadWeatherImage(state.TodayWeather), Today.X, Today.Y);
else if (state.TomorrowWeather != WeatherCondition.Unknown)
else if (state.TomorrowWeather != WeatherCondition.Unknown && Tomorrow != null)
drawer.DrawImage(LoadWeatherImage(state.TomorrowWeather), Tomorrow.X, Tomorrow.Y);
}
private static Bitmap LoadWeatherImage(WeatherCondition weather)
{
var appLocation = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
return (Bitmap) Image.FromFile(System.IO.Path.Combine(appLocation, "WeatherIcons", $"{(int) weather}.png"));
var assembly = Assembly.GetExecutingAssembly();
var imageStream = assembly.GetManifestResourceStream($"WatchFace.Parser.WeatherIcons.{(int) weather}.png");
return (Bitmap) Image.FromStream(imageStream);
}
protected override Element CreateChildForParameter(Parameter parameter)

View File

@ -0,0 +1,63 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using BumpKit;
using WatchFace.Parser.Interfaces;
using WatchFace.Parser.Models;
namespace WatchFace.Parser
{
public class PreviewGenerator
{
public static void CreateGif(List<Parameter> descriptor, Bitmap[] images, Stream outputStream)
{
var previewWatchFace = new Models.Elements.WatchFace(descriptor);
var watchState = new WatchState();
var time = watchState.Time;
using (var encoder = new GifEncoder(outputStream))
{
for (var i = 0; i < 10; i++)
{
var num = i + 1;
watchState.BatteryLevel = num * 10;
watchState.Pulse = 60 + num * 2;
watchState.Steps = num * 1000;
watchState.Calories = num * 75;
watchState.Distance = num * 700;
watchState.Bluetooth = num > 1 && num < 6;
watchState.Unlocked = num > 2 && num < 7;
watchState.Alarm = num > 3 && num < 8;
watchState.DoNotDisturb = num > 4 && num < 9;
watchState.DayTemperature += 2;
watchState.NightTemperature += 4;
watchState.CurrentTemperature += 3;
watchState.Time = new DateTime(time.Year, num, num * 2 + 5, i * 2, i * 6, i);
using (var image = CreateFrame(previewWatchFace, images, watchState))
{
encoder.AddFrame(image, frameDelay: TimeSpan.FromSeconds(1));
}
}
}
}
public static Image CreateImage(IEnumerable<Parameter> descriptor, Bitmap[] resources)
{
var previewWatchFace = new Models.Elements.WatchFace(descriptor);
return CreateFrame(previewWatchFace, resources, new WatchState());
}
private static Image CreateFrame(IDrawable watchFace, Bitmap[] resources, WatchState state)
{
var preview = new Bitmap(176, 176);
var graphics = Graphics.FromImage(preview);
watchFace.Draw(graphics, resources, state);
return preview;
}
}
}

View File

@ -31,5 +31,4 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyVersion("1.0.*")]

View File

@ -31,6 +31,9 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="BumpKit, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Bumpkit.1.0.2\lib\BumpKit.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
@ -148,6 +151,7 @@
<Compile Include="Models\WatchState.cs" />
<Compile Include="Models\AirCondition.cs" />
<Compile Include="Models\WeatherCondition.cs" />
<Compile Include="PreviewGenerator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Reader.cs" />
<Compile Include="Attributes\ParameterImagesCountAttribute.cs" />
@ -169,12 +173,12 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="WeatherIcons\247.png">
<EmbeddedResource Include="WeatherIcons\247.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="WeatherIcons\279.png">
</EmbeddedResource>
<EmbeddedResource Include="WeatherIcons\279.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</EmbeddedResource>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Bumpkit" version="1.0.2" targetFramework="net40-client" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net40-client" />
<package id="NLog" version="4.4.12" targetFramework="net40-client" />
</packages>

View File

@ -9,7 +9,7 @@ using NLog.Config;
using NLog.Targets;
using Resources;
using Resources.Models;
using WatchFace.Parser.Models;
using WatchFace.Parser;
using WatchFace.Parser.Utils;
using Reader = WatchFace.Parser.Reader;
using Writer = WatchFace.Parser.Writer;
@ -126,13 +126,13 @@ namespace WatchFace
var watchFace = ParseResources(reader);
if (watchFace == null) return;
Logger.Debug("Generating preview...");
var previewWatchFace = new Parser.Models.Elements.WatchFace(reader.Parameters);
var preview = new Bitmap(176, 176);
var graphics = Graphics.FromImage(preview);
previewWatchFace.Draw(graphics, reader.Images.ToArray(), new WatchState());
preview.Save(Path.Combine(outputDirectory, $"{baseName}.png"), ImageFormat.Png);
Logger.Debug("Generating previews...");
var preview = PreviewGenerator.CreateImage(reader.Parameters, reader.Images.ToArray());
preview.Save(Path.Combine(outputDirectory, $"{baseName}_preview.png"), ImageFormat.Png);
using (var gifOutput = File.OpenWrite(Path.Combine(outputDirectory, $"{baseName}_preview.gif")))
{
PreviewGenerator.CreateGif(reader.Parameters, reader.Images.ToArray(), gifOutput);
}
Logger.Debug("Exporting resources to '{0}'", outputDirectory);
var reDescriptor = new FileDescriptor {Images = reader.Images};
@ -200,12 +200,12 @@ namespace WatchFace
var descriptor = ParametersConverter.Build(watchFace);
Logger.Debug("Generating preview...");
var previewWatchFace = new Parser.Models.Elements.WatchFace(descriptor);
var preview = new Bitmap(176, 176);
var graphics = Graphics.FromImage(preview);
previewWatchFace.Draw(graphics, imagesReader.Images.ToArray(), new WatchState());
var preview = PreviewGenerator.CreateImage(descriptor, imagesReader.Images.ToArray());
preview.Save(Path.ChangeExtension(outputFileName, ".png"));
using (var gifOutput = File.OpenWrite(Path.ChangeExtension(outputFileName, ".gif")))
{
PreviewGenerator.CreateGif(descriptor, imagesReader.Images.ToArray(), gifOutput);
}
Logger.Debug("Writing watch face to '{0}'", outputFileName);
using (var fileStream = File.OpenWrite(outputFileName))
@ -218,6 +218,7 @@ namespace WatchFace
catch (Exception e)
{
Logger.Fatal(e.Message);
File.Delete(outputFileName);
}
}

View File

@ -31,5 +31,4 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.1.0")]
[assembly: AssemblyFileVersion("1.0.1.0")]
[assembly: AssemblyVersion("1.0.*")]