Compare commits

...

26 Commits

Author SHA1 Message Date
Valeriy Mironov 9c028a9c07 Added unpacking of 32-bit images appeared in some new official WFs 2019-05-04 11:47:08 +03:00
Valeriy Mironov 4ad3537a26 Added support for non image resources 2019-05-04 11:44:19 +03:00
Valeriy Mironov baa352661d Revert "Added unpacking of 32-bit images appeared in some new official WFs"
This reverts commit 1c3663b65d.
2019-05-02 20:45:29 +03:00
Valeriy Mironov 948fd521cc WIP on new elemens 2019-02-02 23:22:00 +02:00
Valeriy Mironov 1c3663b65d Added unpacking of 32-bit images appeared in some new official WFs 2018-08-20 01:53:54 +03:00
Valeriy Mironov 10273531cf Fixed error on searching images 2018-06-09 15:12:24 +03:00
Valeriy Mironov 7d0938b569 Animated preview generation code now read states from a PreviewStates.json file, preview generated using Mono runtime will have separate .png files instead of 1 .gif, added support for drawing correct weather icon depending on weather condition in state 2018-06-09 12:31:09 +03:00
Valeriy Mironov 37ca2a8142 Updated Changelog 2018-06-07 20:40:43 +00:00
Valeriy Mironov f2cc221072 Added support for air quality index value, animated preview now shows discharging battery 2018-06-07 23:35:26 +03:00
Valeriy Mironov b09c78a2a2 Improved speed of all operations by tuning logging options 2018-05-27 15:20:11 +03:00
Valeriy Mironov dd2421c7b4 Updated changelog 2018-04-30 22:03:44 +00:00
Valeriy Mironov cfff020bc0 Skipped storing empty paramter lists. It was possible to brick watches with this :( 2018-05-01 00:54:51 +03:00
Valeriy Mironov 985fd1688e Updated changelog 2018-03-25 08:48:54 +00:00
Valeriy Mironov 631b90fcb5 Added sorting palette of images (fixes #23) 2018-03-25 11:44:16 +03:00
Valeriy Mironov 320be4c269 Updated changelog 2018-02-01 23:31:26 +00:00
Valeriy Mironov e906ed4ff2 Added support for packing a new format of .res-file (fixes #18) 2018-02-02 01:23:37 +02:00
Valeriy Mironov 2398ab8086 Added reading new format of .res-files, fixed order of drawing AmPm element 2018-01-27 20:57:41 +02:00
Valeriy Mironov 950462fb90 Updated changelog 2018-01-13 16:08:56 +00:00
Valeriy Mironov 7ef631924d Added support for custom weather icon 2018-01-13 17:43:34 +02:00
Valeriy Mironov 3e6f8c44fe Removed using C#7 features 2018-01-10 10:27:36 +02:00
Valeriy Mironov 6074eeb335 Fixed appearing of zero DrawingOrder for watchfaces without it 2017-12-25 19:58:21 +02:00
Valeriy Mironov 20f7155838 Updated Changelog 2017-12-25 11:14:20 +00:00
Valeriy Mironov bb8388e95c Fixed drawing weather icon when alt coordinates isn't present 2017-12-25 13:04:29 +02:00
Valeriy Mironov 1957abecbc Added support for time elements drawing order, fixed displaying time when DrawingOrder isn't present, added drawing preview for state when current temperature isn't available 2017-12-25 12:58:20 +02:00
Valeriy Mironov c28196305f Fixed typo in debug message 2017-12-15 01:50:57 +02:00
Valeriy Mironov ee9660dc5a Updated changelog 2017-12-14 21:30:23 +00:00
92 changed files with 1210 additions and 334 deletions

View File

@ -4,6 +4,7 @@
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/PLACE_ACCESSORHOLDER_ATTRIBUTE_ON_SAME_LINE_EX/@EntryValue">NEVER</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/PsiConfigurationSettingsKey/CustomLocation/@EntryValue">C:\Users\valeronm\AppData\Local\JetBrains\Transient\ReSharperPlatformVs15\v11_048a238b\SolutionCaches</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>

View File

@ -3,7 +3,53 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
## [Unreleased]
## [1.0.2.12] - 2018-05-01
### Added
- Added support for air quality index value;
- Aanimated preview now shows discharging battery instead of charging.
## [1.0.2.11] - 2018-05-01
### Changed
- Improved speed of all operations by tuning logging options
## [1.0.2.10] - 2018-05-01
### Added
- Skipped storing empty paramter lists. It was possible to brick watches with this :( (Thanks to Luca Venturini for the report)
## [1.0.2.9] - 2018-03-25
### Added
- Added sorting of image palette for correct repacking font images added in 0.1.1.15 firmware (RES 32) (#23)
## [1.0.2.8] - 2018-02-02
### Added
- Added support for unpacking/packing new format of .res-file from firmware 0.1.0.66 (#18)
### Changed
- Fixed drawing order of AM/PM element.
## [1.0.2.7] - 2018-01-18
### Added
- Added support for custom weather icons. New element `CustomIcon` was added to `Weather.Icon` element.
It contains `X`, `Y`, `ImageIndex` and `ImagesCount` parameters.
`X` and `Y` are the coordinates of the icon on the screen.
`ImageIndex` and `ImagesCount` describe the images set used for weather icon.
## [1.0.2.6] - 2017-12-25
### Changed
- Fixed adding zero `DrawingOrder` when it's not present in config
## [1.0.2.5] - 2017-12-25
### Added
- Added support for digits drawing order in Time block used by new official watchface (Winter).
### Changed
- Renamed `Unknown3` property of `Weather.Icon` element to `CurrentAlt`.
- Renamed `Unknown3` and `Unknown4` properties of `Weather.Temperature.Today.Separate` element to `DayAlt` and `NightAlt`.
## [1.0.2.4] - 2017-12-14
### Changed
- Changed calculated block element position according to watches behavior. Text with width bigger than block width will be rendered with left alignment and text with height bigger than block height will be rendered with top alighnment.
- Fixed error "The image has (5,6,7...) bit/pixel". It was caused by not applying dithering to alpha-channel (#8).
- Dithering is now applied right after image load, so preview uses dithered instead of original image.
## [1.0.2.3] - 2017-12-10
### Changed
@ -55,7 +101,16 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Implemented watchfaces unpacking and packing.
- Implemented .res file unpacking.
[Unreleased]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/HEAD..1.0.2.3
[Unreleased]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/HEAD..1.0.2.12
[1.0.2.12]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.12..1.0.2.11
[1.0.2.11]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.11..1.0.2.10
[1.0.2.10]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.10..1.0.2.9
[1.0.2.9]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.9..1.0.2.8
[1.0.2.8]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.8..1.0.2.7
[1.0.2.7]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.7..1.0.2.6
[1.0.2.6]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.6..1.0.2.5
[1.0.2.5]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.5..1.0.2.4
[1.0.2.4]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.4..1.0.2.3
[1.0.2.3]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.3..1.0.2.2
[1.0.2.2]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.2..1.0.2.1
[1.0.2.1]: https://bitbucket.org/valeronm/amazfitbiptools/branches/compare/1.0.2.1..1.0.2.0

View File

@ -18,22 +18,16 @@ namespace Resources
public void Extract(string outputDirectory)
{
if (_descriptor.Version != null)
{
var fileName = Path.Combine(outputDirectory, "version");
using (var stream = File.OpenWrite(fileName))
using (var writer = new BinaryWriter(stream))
{
writer.Write(_descriptor.Version.Value);
}
}
for (var i = 0; i < _descriptor.Images.Count; i++)
for (var i = 0; i < _descriptor.Resources.Count; i++)
{
var resource = _descriptor.Resources[i];
var numericPart = i.ToString().PadLeft(ImageLoader.NumericPartLength, '0');
var fileName = Path.Combine(outputDirectory, numericPart + ".png");
var fileName = Path.Combine(outputDirectory, numericPart + resource.Extension);
Logger.Debug("Extracting {0}...", fileName);
_descriptor.Images[i].Save(fileName, ImageFormat.Png);
using (var fileStream = File.OpenWrite(fileName))
resource.ExportTo(fileStream);
}
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Text;
using NLog;
using Resources.Models;
@ -13,20 +14,33 @@ namespace Resources
{
var binaryReader = new BinaryReader(stream);
Logger.Trace("Reading resources header");
var header = Header.ReadFrom(binaryReader);
Logger.Trace("Resources header was read:");
Logger.Trace("Signature: {0}, Version: {1}, ResourcesCount: {2}, IsValid: {3}",
header.Signature, header.Version, header.ResourcesCount, header.IsValid
);
var signature = Encoding.ASCII.GetString(binaryReader.ReadBytes(5));
Logger.Trace("Resources signature was read:");
stream.Seek(0, SeekOrigin.Begin);
Logger.Trace("Signature: {0}", signature);
if (!header.IsValid)
throw new ArgumentException("Invalid resources header");
Header header;
switch (signature) {
case Header.ResSignature:
header = Header.ReadFrom(binaryReader);
break;
case NewHeader.ResSignature:
header = NewHeader.ReadFrom(binaryReader);
break;
default:
throw new ArgumentException($"Signature '{signature}' is no recognized.");
}
Logger.Trace("Resources header was read:");
Logger.Trace("Version: {0}, ResourcesCount: {1}", header.Version, header.ResourcesCount);
return new FileDescriptor
{
HasNewHeader = header is NewHeader,
ResourcesCount = header.ResourcesCount,
Version = header.Version,
Images = new Reader(stream).Read(header.ResourcesCount)
Unknown = (header as NewHeader)?.Unknown,
Resources = new Reader(stream).Read(header.ResourcesCount)
};
}
}

View File

@ -23,19 +23,43 @@ namespace Resources
if (descriptor.Version == null)
throw new ArgumentException("Res file version required");
if (descriptor.HasNewHeader)
WriteNewHeader(descriptor);
else
WriteOldHeader(descriptor);
Logger.Trace("Writing images...");
new Writer(_stream).Write(descriptor.Resources);
}
private void WriteOldHeader(FileDescriptor descriptor)
{
var header = new Header
{
ResourcesCount = (uint) descriptor.Images.Count,
ResourcesCount = (uint)descriptor.Resources.Count,
Version = descriptor.Version.Value
};
Logger.Trace("Writing resources header...");
Logger.Trace("Signature: {0}, Version: {1}, ResourcesCount: {2}, IsValid: {3}",
header.Signature, header.Version, header.ResourcesCount, header.IsValid
Logger.Trace("Signature: {0}, Version: {1}, ResourcesCount: {2}",
header.Signature, header.Version, header.ResourcesCount
);
header.WriteTo(_binaryWriter);
Logger.Trace("Writing images...");
new Writer(_stream).Write(descriptor.Images);
}
private void WriteNewHeader(FileDescriptor descriptor)
{
var header = new NewHeader
{
ResourcesCount = descriptor.ResourcesCount.Value,
Version = descriptor.Version.Value,
Unknown = descriptor.Unknown.Value
};
Logger.Trace("Writing resources header...");
Logger.Trace("Signature: {0}, Version: {1}, ResourcesCount: {2}, Unknown: {3}",
header.Signature, header.Version, header.ResourcesCount, header.Unknown
);
header.WriteTo(_binaryWriter);
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Drawing;
using System.Drawing;
using System.IO;
using BumpKit;
using NLog;
@ -29,10 +28,20 @@ namespace Resources.Image
{
var signature = _reader.ReadChars(4);
if (signature[0] != 'B' || signature[1] != 'M')
throw new ArgumentException("Image signature doesn't match.");
throw new InvalidResourceException("Image signature doesn't match.");
ReadHeader();
ReadPalette();
if (_paletteColors > 256)
throw new InvalidResourceException(
"Too many palette colors.");
if (_paletteColors > 0)
ReadPalette();
else if (_bitsPerPixel == 8 || _bitsPerPixel == 16 || _bitsPerPixel == 24 || _bitsPerPixel == 32)
Logger.Trace("The image doesn't use a palette.");
else
throw new InvalidResourceException(
"The image format is not supported. Please report the issue on https://bitbucket.org/valeronm/amazfitbiptools");
return ReadImage();
}
@ -66,7 +75,6 @@ namespace Resources.Image
if (padding != 0) Logger.Warn("Palette item {0} last byte is not zero: {1:X2}", i, padding);
var isColorValid = (r == 0 || r == 0xff) && (g == 0 || g == 0xff) && (b == 0 || b == 0xff);
if (isColorValid)
Logger.Trace("Palette item {0}: R {1:X2}, G {2:X2}, B {3:X2}", i, r, g, b);
else
@ -78,6 +86,16 @@ namespace Resources.Image
}
private Bitmap ReadImage()
{
if (_paletteColors > 0) return ReadPaletteImage();
if (_bitsPerPixel == 8) return Read8BitImage();
if (_bitsPerPixel == 16) return Read16BitImage();
if (_bitsPerPixel == 24) return Read24BitImage();
if (_bitsPerPixel == 32) return Read32BitImage();
throw new InvalidResourceException($"Unsupported bits per pixel value: {_bitsPerPixel}");
}
private Bitmap ReadPaletteImage()
{
var image = new Bitmap(_width, _height);
using (var context = image.CreateUnsafeContext())
@ -89,11 +107,104 @@ namespace Resources.Image
for (var x = 0; x < _width; x++)
{
var pixelColorIndex = bitReader.ReadBits(_bitsPerPixel);
var color = _palette[(int) pixelColorIndex];
var color = _palette[(int)pixelColorIndex];
context.SetPixel(x, y, color);
}
}
}
return image;
}
private Bitmap Read8BitImage()
{
var image = new Bitmap(_width, _height);
using (var context = image.CreateUnsafeContext())
{
for (var y = 0; y < _height; y++)
{
var rowBytes = _reader.ReadBytes(_rowLengthInBytes);
for (var x = 0; x < _width; x++)
{
var data = rowBytes[x];
var color = Color.FromArgb(0xff, data, data, data);
context.SetPixel(x, y, color);
}
}
}
return image;
}
private Bitmap Read16BitImage()
{
var image = new Bitmap(_width, _height);
using (var context = image.CreateUnsafeContext())
{
for (var y = 0; y < _height; y++)
{
var rowBytes = _reader.ReadBytes(_rowLengthInBytes);
var bitReader = new BitReader(rowBytes);
for (var x = 0; x < _width; x++)
{
var firstByte = (int)bitReader.ReadByte();
var secondByte = (int)bitReader.ReadByte();
var b = (byte)((secondByte >> 3) & 0x1f) << 3;
var g = (byte)(((firstByte >> 5) & 0x7) | ((secondByte & 0x07) << 3)) << 2;
var r = (byte)(firstByte & 0x1f) << 3;
var color = Color.FromArgb(0xff, r, g, b);
context.SetPixel(x, y, color);
}
}
}
return image;
}
private Bitmap Read24BitImage()
{
var image = new Bitmap(_width, _height);
using (var context = image.CreateUnsafeContext())
{
for (var y = 0; y < _height; y++)
{
var rowBytes = _reader.ReadBytes(_rowLengthInBytes);
var bitReader = new BitReader(rowBytes);
for (var x = 0; x < _width; x++)
{
var alpha = (int)bitReader.ReadByte();
var b = (int)(bitReader.ReadBits(5) << 3);
var g = (int)(bitReader.ReadBits(6) << 2);
var r = (int)(bitReader.ReadBits(5) << 3);
var color = Color.FromArgb(0xff - alpha, r, g, b);
context.SetPixel(x, y, color);
}
}
}
return image;
}
private Bitmap Read32BitImage()
{
var image = new Bitmap(_width, _height);
using (var context = image.CreateUnsafeContext())
{
for (var y = 0; y < _height; y++)
{
var rowBytes = _reader.ReadBytes(_rowLengthInBytes);
for (var x = 0; x < _width; x++)
{
var r = rowBytes[x * 4];
var g = rowBytes[x * 4 + 1];
var b = rowBytes[x * 4 + 2];
var alpha = rowBytes[x * 4 + 3];
var color = Color.FromArgb(0xff - alpha, r, g, b);
context.SetPixel(x, y, color);
}
}
}
return image;
}
}

View File

@ -44,7 +44,7 @@ namespace Resources.Image
if (_bitsPerPixel > 4)
throw new ArgumentException(
$"The image has {_bitsPerPixel} bit/pixel and can't be packed for using on the watches. Looks like dithering works wincorrectly on the image."
$"The image has {_bitsPerPixel} bit/pixel and can't be packed for using on the watches. Looks like dithering works incorrectly on the image."
);
_rowLengthInBytes = (ushort) Math.Ceiling(_width * _bitsPerPixel / 8.0);
@ -85,6 +85,29 @@ namespace Resources.Image
}
}
}
var startIndex = _transparency == 0 ? 0 : 1;
for (var i = startIndex; i < _palette.Count - 1; i++)
{
var minColor = (uint) _palette[i].ToArgb();
var minIndex = i;
for (var j = i + 1; j < _palette.Count; j++)
{
var color = (uint) _palette[j].ToArgb();
if (color >= minColor) continue;
minColor = color;
minIndex = j;
}
if (minIndex == i) continue;
var tmp = _palette[i];
_palette[i] = _palette[minIndex];
_palette[minIndex] = tmp;
}
_paletteColors = (ushort) _palette.Count;
_bitsPerPixel = (ushort) Math.Ceiling(Math.Log(_paletteColors, 2));
}
@ -149,6 +172,7 @@ namespace Resources.Image
bitWriter.WriteBits(paletteIndex, _bitsPerPixel);
}
}
bitWriter.Flush();
_writer.Write(rowData);
}

View File

@ -1,42 +1,71 @@
using System.Drawing;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using NLog;
using Resources.Image;
using Resources.Models;
namespace Resources
{
public class ImageLoader
{
public static readonly int NumericPartLength = 3;
public static readonly int NumericPartLength = 4;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public static Bitmap LoadImageForNumber(string directory, long index)
public static IResource LoadResourceForNumber(string directory, long index)
{
var strIndex = index.ToString();
var numericParts = new[]
{
index.ToString().PadLeft(NumericPartLength, '0'),
index.ToString()
};
strIndex.PadLeft(4, '0'), strIndex.PadLeft(3, '0'), strIndex.PadLeft(2, '0'), strIndex
}.Distinct();
foreach (var numericPart in numericParts.Distinct())
foreach (var numericPart in numericParts)
{
var fullFileName = Path.Combine(directory, numericPart + ".png");
if (!File.Exists(fullFileName))
{
Logger.Trace("File {0} doesn't exists.", fullFileName);
continue;
}
var resource = TryLoadBitmap(directory, numericPart);
if (resource != null) return resource;
var image = (Bitmap) System.Drawing.Image.FromFile(fullFileName);
Logger.Trace("Image was loaded from file {0}", fullFileName);
return ApplyDithering(image);
resource = TryLoadBlob(directory, numericPart);
if (resource != null) return resource;
}
throw new FileNotFoundException($"File referenced by index {index} not found.");
}
private static IResource TryLoadBitmap(string directory, string numericPart)
{
var fullFileName = Path.Combine(directory, numericPart + Models.Image.ResourceExtension);
if (!File.Exists(fullFileName))
{
Logger.Trace("File {0} doesn't exists.", fullFileName);
return null;
}
var image = (Bitmap) System.Drawing.Image.FromFile(fullFileName);
Logger.Trace("Image was loaded from file {0}", fullFileName);
var ditheredBitmap = ApplyDithering(image);
return new Models.Image(ditheredBitmap);
}
private static IResource TryLoadBlob(string directory, string numericPart)
{
var fullFileName = Path.Combine(directory, numericPart + Blob.ResourceExtension);
if (!File.Exists(fullFileName))
{
Logger.Trace("File {0} doesn't exists.", fullFileName);
return null;
}
using (var fileStream = File.OpenRead(fullFileName))
{
var data = new byte[fileStream.Length];
fileStream.Read(data, 0, data.Length);
return new Blob(data);
}
}
private static Bitmap ApplyDithering(Bitmap image)
{
var clone = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppArgb);
@ -46,7 +75,6 @@ namespace Resources
}
FloydSteinbergDitherer.Process(clone);
clone.Save("tmp.png");
return clone;
}
}

View File

@ -0,0 +1,9 @@
using System;
namespace Resources
{
public class InvalidResourceException : Exception
{
public InvalidResourceException(string message) : base(message) { }
}
}

27
Resources/Models/Blob.cs Normal file
View File

@ -0,0 +1,27 @@
using System.IO;
namespace Resources.Models
{
public class Blob : IResource
{
public static string ResourceExtension = ".dat";
private readonly byte[] _data;
public Blob(byte[] data)
{
_data = data;
}
public string Extension => ResourceExtension;
public void WriteTo(Stream stream)
{
stream.Write(_data, 0, _data.Length);
}
public void ExportTo(Stream stream)
{
stream.Write(_data, 0, _data.Length);
}
}
}

View File

@ -1,11 +1,18 @@
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.Serialization;
namespace Resources.Models
{
public class FileDescriptor
{
public bool HasNewHeader { get; set; }
public uint? ResourcesCount { get; set; }
public uint? Unknown { get; set; }
public byte? Version { get; set; }
public List<Bitmap> Images { get; set; } = new List<Bitmap>();
[IgnoreDataMember]
public List<IResource> Resources { get; set; } = new List<IResource>();
}
}

View File

@ -7,15 +7,13 @@ namespace Resources.Models
public class Header
{
public const int HeaderSize = 20;
private const string ResSignature = "HMRES";
public const string ResSignature = "HMRES";
public string Signature { get; private set; } = ResSignature;
public string Signature { get; protected set; } = ResSignature;
public byte Version { get; set; }
public uint ResourcesCount { get; set; }
public bool IsValid => Signature == ResSignature;
public void WriteTo(BinaryWriter writer)
public virtual void WriteTo(BinaryWriter writer)
{
var buffer = new byte[HeaderSize];
for (var i = 0; i < buffer.Length; i++) buffer[i] = 0xff;

View File

@ -0,0 +1,11 @@
using System.IO;
namespace Resources.Models
{
public interface IResource
{
string Extension { get; }
void WriteTo(Stream stream);
void ExportTo(Stream stream);
}
}

29
Resources/Models/Image.cs Normal file
View File

@ -0,0 +1,29 @@
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace Resources.Models
{
public class Image: IResource
{
public Bitmap Bitmap { get; }
public Image(Bitmap bitmap)
{
Bitmap = bitmap;
}
public static string ResourceExtension = ".png";
public string Extension => ResourceExtension;
public void WriteTo(Stream stream)
{
new Resources.Image.Writer(stream).Write(Bitmap);
}
public void ExportTo(Stream stream)
{
Bitmap.Save(stream, ImageFormat.Png);
}
}
}

View File

@ -0,0 +1,43 @@
using System;
using System.IO;
using System.Text;
namespace Resources.Models
{
public class NewHeader : Header
{
public new const int HeaderSize = 0x24;
public new const string ResSignature = "NERES";
public NewHeader()
{
Signature = ResSignature;
}
public uint Unknown { get; set; }
public override void WriteTo(BinaryWriter writer)
{
var buffer = new byte[HeaderSize];
for (var i = 0; i < buffer.Length; i++) buffer[i] = 0xff;
Encoding.ASCII.GetBytes(ResSignature).CopyTo(buffer, 0);
buffer[5] = Version;
BitConverter.GetBytes(Unknown).CopyTo(buffer, 0xa);
BitConverter.GetBytes(ResourcesCount).CopyTo(buffer, 0x20);
writer.Write(buffer);
}
public new static NewHeader ReadFrom(BinaryReader reader)
{
var buffer = reader.ReadBytes(HeaderSize);
return new NewHeader
{
Signature = Encoding.ASCII.GetString(buffer, 0, 0x5),
Version = buffer[0x5],
Unknown = BitConverter.ToUInt32(buffer, 0xa),
ResourcesCount = BitConverter.ToUInt32(buffer, 0x20)
};
}
}
}

View File

@ -1,8 +1,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Collections.Generic;
using System.IO;
using NLog;
using Resources.Models;
namespace Resources
{
@ -20,32 +19,54 @@ namespace Resources
_binaryReader = new BinaryReader(_stream);
}
public List<Bitmap> Read(uint imagesTableLength)
public List<IResource> Read(uint resourcesCount)
{
var offsetsTableLength = (int) (imagesTableLength * OffsetTableItemLength);
var offsetsTableLength = (int) (resourcesCount * OffsetTableItemLength);
Logger.Trace("Reading resources offsets table with {0} elements ({1} bytes)",
imagesTableLength, offsetsTableLength
resourcesCount, offsetsTableLength
);
var imagesOffsets = _binaryReader.ReadBytes(offsetsTableLength);
var imagesOffset = _stream.Position;
Logger.Debug("Reading {0} images...", imagesTableLength);
var images = new List<Bitmap>((int) imagesTableLength);
for (var i = 0; i < imagesTableLength; i++)
var offsets = new int[resourcesCount];
for (var i = 0; i < resourcesCount; i++)
offsets[i] = _binaryReader.ReadInt32();
var resourcesOffset = (int) _stream.Position;
var fileSize = (int) _stream.Length;
Logger.Debug("Reading {0} resources...", resourcesCount);
var resources = new List<IResource>((int) resourcesCount);
for (var i = 0; i < resourcesCount; i++)
{
var imageOffset = BitConverter.ToUInt32(imagesOffsets, i * OffsetTableItemLength);
var realOffset = imageOffset + imagesOffset;
Logger.Trace("Image {0} offset is {1}...", i, imageOffset);
if (_stream.Position != realOffset)
var offset = offsets[i] + resourcesOffset;
var nextOffset = i + 1 < resourcesCount ? offsets[i + 1] + resourcesOffset : fileSize;
var length = nextOffset - offset;
Logger.Trace("Resource {0} offset: {1}, length: {2}...", i, offset, length);
if (_stream.Position != offset)
{
var bytesGap = realOffset - _stream.Position;
var bytesGap = offset - _stream.Position;
Logger.Warn("Found {0} bytes gap before resource number {1}", bytesGap, i);
_stream.Seek(realOffset, SeekOrigin.Begin);
_stream.Seek(offset, SeekOrigin.Begin);
}
Logger.Debug("Reading resource {0}...", i);
try
{
var bitmap = new Image.Reader(_stream).Read();
var image = new Models.Image(bitmap);
resources.Add(image);
}
catch (InvalidResourceException)
{
Logger.Warn("Resource is not an image");
_stream.Seek(offset, SeekOrigin.Begin);
var data = new byte[length];
_stream.Read(data, 0, length);
var blob = new Blob(data);
resources.Add(blob);
}
Logger.Debug("Reading image {0}...", i);
images.Add(new Image.Reader(_stream).Read());
}
return images;
return resources;
}
}
}

12
Resources/Resource.cs Normal file
View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Resources
{
interface IResource
{
}
}

View File

@ -35,11 +35,15 @@
<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>
<HintPath>..\packages\NLog.4.5.6\lib\net40-client\NLog.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
@ -47,17 +51,22 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Extractor.cs" />
<Compile Include="ImageLoader.cs" />
<Compile Include="InvalidResourceException.cs" />
<Compile Include="Models\Blob.cs" />
<Compile Include="Models\Image.cs" />
<Compile Include="Models\IResource.cs" />
<Compile Include="Models\FileDescriptor.cs" />
<Compile Include="Models\Header.cs" />
<Compile Include="Utils\BitReader.cs" />
<Compile Include="Utils\BitWriter.cs" />
<Compile Include="Image\ColorError.cs" />
<Compile Include="Image\FloydSteinbergDitherer.cs" />
<Compile Include="Image\Writer.cs" />
<Compile Include="Writer.cs" />
<Compile Include="Extractor.cs" />
<Compile Include="FileWriter.cs" />
<Compile Include="Models\Header.cs" />
<Compile Include="Models\NewHeader.cs" />
<Compile Include="Image\Reader.cs" />
<Compile Include="FileReader.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp60</s:String></wpf:ResourceDictionary>

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Drawing;
using System.IO;
using NLog;
using Resources.Models;
namespace Resources
{
@ -18,30 +19,30 @@ namespace Resources
_stream = stream;
}
public void Write(List<Bitmap> images)
public void Write(List<IResource> resources)
{
var offsetsTable = new byte[images.Count * OffsetTableItemLength];
var encodedImages = new MemoryStream[images.Count];
var offsetsTable = new byte[resources.Count * OffsetTableItemLength];
var encodedResources = new MemoryStream[resources.Count];
var offset = (uint) 0;
for (var i = 0; i < images.Count; i++)
for (var i = 0; i < resources.Count; i++)
{
Logger.Trace("Image {0} offset is {1}...", i, offset);
Logger.Trace("Resource {0} offset is {1}...", i, offset);
var offsetBytes = BitConverter.GetBytes(offset);
offsetBytes.CopyTo(offsetsTable, i * OffsetTableItemLength);
var encodedImage = new MemoryStream();
Logger.Debug("Encoding image {0}...", i);
new Image.Writer(encodedImage).Write(images[i]);
Logger.Debug("Encoding resource {0}...", i);
resources[i].WriteTo(encodedImage);
offset += (uint) encodedImage.Length;
encodedImages[i] = encodedImage;
encodedResources[i] = encodedImage;
}
Logger.Trace("Writing images offsets table");
Logger.Trace("Writing resources offsets table");
_stream.Write(offsetsTable, 0, offsetsTable.Length);
Logger.Debug("Writing images");
foreach (var encodedImage in encodedImages)
Logger.Debug("Writing resources");
foreach (var encodedImage in encodedResources)
{
encodedImage.Seek(0, SeekOrigin.Begin);
encodedImage.CopyTo(_stream);

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" />
<package id="NLog" version="4.5.6" targetFramework="net40-client" />
</packages>

View File

@ -15,5 +15,8 @@ namespace WatchFace.Parser.Elements.ActivityElements
[ParameterId(3)]
[ParameterImageIndex]
public long? DecimalPointImageIndex { get; set; }
[ParameterId(4)]
public long? SuffixMilesImageIndex { get; set; }
}
}

View File

@ -11,7 +11,7 @@ namespace WatchFace.Parser.Elements.AnalogDialFaceElements
[ParameterId(1)]
public bool OnlyBorder { get; set; }
[JsonConverter(typeof(HexStringJsonConverter))]
[JsonConverter(typeof(ColorJsonConverter))]
[ParameterId(2)]
public long Color { get; set; }

View File

@ -27,7 +27,7 @@ namespace WatchFace.Parser.Elements.BasicElements
[ParameterId(7)]
public long Width { get; set; }
[JsonConverter(typeof(HexStringJsonConverter))]
[JsonConverter(typeof(ColorJsonConverter))]
[ParameterId(8)]
public long Color { get; set; }
}

View File

@ -0,0 +1,29 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using WatchFace.Parser.Attributes;
using WatchFace.Parser.Models;
namespace WatchFace.Parser.Elements.BasicElements
{
public class UnknownType
{
[ParameterId(1)]
public long TopLeftX { get; set; }
[ParameterId(2)]
public long TopLeftY { get; set; }
[ParameterId(3)]
public long BottomRightX { get; set; }
[ParameterId(4)]
public long BottomRightY { get; set; }
[ParameterId(5)]
[JsonConverter(typeof(StringEnumConverter))]
public TextAlignment Alignment { get; set; }
[ParameterId(6)]
public long Spacing { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using WatchFace.Parser.Attributes;
using WatchFace.Parser.Models;
namespace WatchFace.Parser.Elements.BasicElements
{
public class UnknownType14d6
{
[ParameterId(1)]
public Coordinates Unknown1 { get; set; }
[ParameterId(2)]
public Coordinates Unknown2 { get; set; }
[ParameterId(3)]
public long? Unknown3 { get; set; }
}
}

View File

@ -13,5 +13,11 @@ namespace WatchFace.Parser.Elements
[ParameterId(3)]
public Scale Scale { get; set; }
[ParameterId(5)]
public long? Unknown5 { get; set; }
[ParameterId(6)]
public long? Unknown6 { get; set; }
}
}

View File

@ -11,5 +11,11 @@ namespace WatchFace.Parser.Elements
[ParameterId(2)]
public ImageSet WeekDay { get; set; }
[ParameterId(3)]
public DateUnknown3 Unknown3 { get; set; }
[ParameterId(4)]
public Coordinates Unknown4 { get; set; }
}
}

View File

@ -0,0 +1,11 @@
using WatchFace.Parser.Attributes;
using WatchFace.Parser.Elements.BasicElements;
namespace WatchFace.Parser.Elements.DateElements
{
public class DateUnknown3
{
[ParameterId(2)]
public UnknownType Unknown2 { get; set; }
}
}

View File

@ -1,5 +1,7 @@
using WatchFace.Parser.Attributes;
using Newtonsoft.Json;
using WatchFace.Parser.Attributes;
using WatchFace.Parser.Elements.TimeElements;
using WatchFace.Parser.JsonConverters;
namespace WatchFace.Parser.Elements
{
@ -16,5 +18,12 @@ namespace WatchFace.Parser.Elements
[ParameterId(4)]
public AmPm AmPm { get; set; }
[JsonConverter(typeof(DrawingOrderJsonConverter))]
[ParameterId(5)]
public long? DrawingOrder { get; set; }
[ParameterId(9)]
public long? Unknown9 { get; set; }
}
}

View File

@ -0,0 +1,26 @@
using Newtonsoft.Json;
using WatchFace.Parser.Attributes;
using WatchFace.Parser.Elements.BasicElements;
using WatchFace.Parser.Elements.TimeElements;
using WatchFace.Parser.JsonConverters;
namespace WatchFace.Parser.Elements
{
public class UnknownType14
{
[ParameterId(1)]
public TwoDigits Unknown1 { get; set; }
[ParameterId(2)]
public TwoDigits Unknown2 { get; set; }
[ParameterId(6)]
public UnknownType14d6 Unknown6 { get; set; }
[ParameterId(7)]
public UnknownType14d6 Unknown7 { get; set; }
[ParameterId(8)]
public UnknownType14d6 Unknown8 { get; set; }
}
}

View File

@ -5,7 +5,8 @@ namespace WatchFace.Parser.Elements.WeatherElements
{
public class AirPollution
{
// TODO: Looks like here should be Id 1 also
[ParameterId(1)]
public Number Index { get; set; }
[ParameterId(2)]
public ImageSet Icon { get; set; }

View File

@ -0,0 +1,21 @@
using WatchFace.Parser.Attributes;
namespace WatchFace.Parser.Elements.WeatherElements
{
public class CustomWeatherIcon
{
[ParameterId(1)]
public long X { get; set; }
[ParameterId(2)]
public long Y { get; set; }
[ParameterId(3)]
[ParameterImageIndex]
public long ImageIndex { get; set; }
[ParameterId(4)]
[ParameterImagesCount]
public long ImagesCount { get; set; }
}
}

View File

@ -1,4 +1,5 @@
using WatchFace.Parser.Attributes;
using Newtonsoft.Json;
using WatchFace.Parser.Attributes;
using WatchFace.Parser.Elements.BasicElements;
namespace WatchFace.Parser.Elements.WeatherElements
@ -12,9 +13,23 @@ namespace WatchFace.Parser.Elements.WeatherElements
public TemperatureNumber Night { get; set; }
[ParameterId(3)]
public Coordinates Unknown3 { get; set; }
public Coordinates DayAlt { get; set; }
[ParameterId(4)]
public Coordinates Unknown4 { get; set; }
public Coordinates NightAlt { get; set; }
// For compatibility with "Unknown3" JSON attribute
[JsonProperty("Unknown3")]
private Coordinates Unknown3
{
set { DayAlt = value; }
}
// For compatibility with "Unknown4" JSON attribute
[JsonProperty("Unknown4")]
private Coordinates Unknown4
{
set { NightAlt = value; }
}
}
}

View File

@ -1,4 +1,6 @@
using WatchFace.Parser.Attributes;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using WatchFace.Parser.Attributes;
using WatchFace.Parser.Elements.BasicElements;
namespace WatchFace.Parser.Elements.WeatherElements
@ -8,12 +10,20 @@ namespace WatchFace.Parser.Elements.WeatherElements
[ParameterId(1)]
public Coordinates Coordinates { get; set; }
// TODO: Looks like here should be Id 2 also
[ParameterId(2)]
public CustomWeatherIcon CustomIcon { get; set; }
[ParameterId(3)]
public Coordinates Unknown3 { get; set; }
public Coordinates CoordinatesAlt { get; set; }
[ParameterId(4)]
public Coordinates Unknown4 { get; set; }
// For compatibility with "Unknown3" JSON attribute
[JsonProperty("Unknown3")]
private Coordinates Unknown3
{
set { CoordinatesAlt = value; }
}
}
}

View File

@ -3,7 +3,7 @@ using Newtonsoft.Json;
namespace WatchFace.Parser.JsonConverters
{
public class HexStringJsonConverter : JsonConverter
public class ColorJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{

View File

@ -0,0 +1,27 @@
using System;
using Newtonsoft.Json;
namespace WatchFace.Parser.JsonConverters
{
public class DrawingOrderJsonConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue($"{value:X}");
}
public override bool CanConvert(Type objectType)
{
return typeof(uint) == objectType;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
var str = (string) reader.Value;
if (str == null)
throw new JsonSerializationException();
return Convert.ToInt64(str, 16);
}
}
}

View File

@ -0,0 +1,10 @@
namespace WatchFace.Parser.Models
{
public enum DrawingOrderPosition
{
HourTens = 1,
HourOnes = 2,
MinuteTens = 3,
MinuteOnes = 4
}
}

View File

@ -22,6 +22,11 @@ namespace WatchFace.Parser.Models.Elements
return new Rectangle((int) X, (int) Y, (int) (BottomRightX - X), (int) (BottomRightY - Y));
}
public Rectangle GetAltBox(CoordinatesElement altCoordinates)
{
return new Rectangle((int) altCoordinates.X, (int) altCoordinates.Y, (int) (BottomRightX - X), (int) (BottomRightY - Y));
}
public void Draw(Graphics drawer, Bitmap[] images, int number, int minimumDigits = 1)
{
DrawerHelper.DrawImages(drawer, GetImagesForNumber(images, number, minimumDigits), (int) Spacing, Alignment, GetBox());

View File

@ -1,18 +0,0 @@
using System.Drawing;
using WatchFace.Parser.Interfaces;
namespace WatchFace.Parser.Models.Elements
{
public class HoursElement : TwoDigitsElement, IDrawable
{
public HoursElement(Parameter parameter, Element parent, string name = null) :
base(parameter, parent, name) { }
public void Draw(Graphics drawer, Bitmap[] resources, WatchState state)
{
var timeElement = (TimeElement) _parent;
var hours = timeElement.AmPm == null ? state.Time.Hour : state.Time.Hour % 12;
Draw(drawer, resources, hours);
}
}
}

View File

@ -1,16 +0,0 @@
using System.Drawing;
using WatchFace.Parser.Interfaces;
namespace WatchFace.Parser.Models.Elements
{
public class MinutesElement : TwoDigitsElement, IDrawable
{
public MinutesElement(Parameter parameter, Element parent, string name = null) :
base(parameter, parent, name) { }
public void Draw(Graphics drawer, Bitmap[] resources, WatchState state)
{
Draw(drawer, resources, state.Time.Minute);
}
}
}

View File

@ -1,16 +0,0 @@
using System.Drawing;
using WatchFace.Parser.Interfaces;
namespace WatchFace.Parser.Models.Elements
{
public class SecondsElement : TwoDigitsElement, IDrawable
{
public SecondsElement(Parameter parameter, Element parent, string name = null) :
base(parameter, parent, name) { }
public void Draw(Graphics drawer, Bitmap[] resources, WatchState state)
{
Draw(drawer, resources, state.Time.Second);
}
}
}

View File

@ -1,31 +1,71 @@
namespace WatchFace.Parser.Models.Elements
using System.Drawing;
using NLog;
using WatchFace.Parser.Utils;
namespace WatchFace.Parser.Models.Elements
{
public class TimeElement : ContainerElement
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public TimeElement(Parameter parameter, Element parent = null, string name = null) :
base(parameter, parent, name) { }
public HoursElement Hours { get; set; }
public MinutesElement Minutes { get; set; }
public SecondsElement Seconds { get; set; }
public TwoDigitsElement Hours { get; set; }
public TwoDigitsElement Minutes { get; set; }
public TwoDigitsElement Seconds { get; set; }
public AmPmElement AmPm { get; set; }
public long? DrawingOrder { get; set; }
public override void Draw(Graphics drawer, Bitmap[] images, WatchState state)
{
AmPm?.Draw(drawer, images, state);
var hours = AmPm == null ? state.Time.Hour : state.Time.Hour % 12;
var drawingOrder = DrawingOrder ?? 0x1234;
foreach (var position in DrawingOrderIterator.Iterate(drawingOrder))
switch (position)
{
case DrawingOrderPosition.HourTens:
Hours?.Tens?.Draw(drawer, images, hours % 100 / 10);
break;
case DrawingOrderPosition.HourOnes:
Hours?.Ones?.Draw(drawer, images, hours % 10);
break;
case DrawingOrderPosition.MinuteTens:
Minutes?.Tens?.Draw(drawer, images, state.Time.Minute % 100 / 10);
break;
case DrawingOrderPosition.MinuteOnes:
Minutes?.Ones?.Draw(drawer, images, state.Time.Minute % 10);
break;
default:
Logger.Warn("Not supported element {0} in DrawingOrder value", position);
break;
}
Seconds?.Draw(drawer, images, state.Time.Second);
}
protected override Element CreateChildForParameter(Parameter parameter)
{
switch (parameter.Id)
{
case 1:
Hours = new HoursElement(parameter, this, nameof(Hours));
Hours = new TwoDigitsElement(parameter, this, nameof(Hours));
return Hours;
case 2:
Minutes = new MinutesElement(parameter, this, nameof(Minutes));
Minutes = new TwoDigitsElement(parameter, this, nameof(Minutes));
return Minutes;
case 3:
Seconds = new SecondsElement(parameter, this, nameof(Seconds));
Seconds = new TwoDigitsElement(parameter, this, nameof(Seconds));
return Seconds;
case 4:
AmPm = new AmPmElement(parameter, this, nameof(AmPm));
return AmPm;
case 5:
DrawingOrder = parameter.Value;
return new ValueElement(parameter, this, nameof(DrawingOrder));
default:
return base.CreateChildForParameter(parameter);
}

View File

@ -9,9 +9,9 @@ namespace WatchFace.Parser.Models.Elements
public override void Draw(Graphics drawer, Bitmap[] resources, WatchState state)
{
if (state.Air == AirCondition.Unknown) return;
if (state.AirQuality == AirCondition.Unknown) return;
var imageIndex = (int) state.Air;
var imageIndex = (int) state.AirQuality;
Draw(drawer, resources, imageIndex);
}
}

View File

@ -3,15 +3,15 @@ using WatchFace.Parser.Interfaces;
namespace WatchFace.Parser.Models.Elements
{
public class TodayNightTemperatureElement : TemperatureNumberElement, IDrawable
public class AirQualityIndexNumberElement : NumberElement, IDrawable
{
public TodayNightTemperatureElement(Parameter parameter, Element parent = null, string name = null) :
public AirQualityIndexNumberElement(Parameter parameter, Element parent = null, string name = null) :
base(parameter, parent, name) { }
public void Draw(Graphics drawer, Bitmap[] resources, WatchState state)
{
if (state.NightTemperature != null)
Draw(drawer, resources, state.NightTemperature.Value);
if (state.AirQualityIndex != null)
Draw(drawer, resources, state.AirQualityIndex.Value);
}
}
}

View File

@ -5,12 +5,16 @@
public AirPollutionElement(Parameter parameter, Element parent = null, string name = null) :
base(parameter, parent, name) { }
public AirQualityIndexNumberElement Index { get; set; }
public AirPollutionImageElement Current { get; set; }
protected override Element CreateChildForParameter(Parameter parameter)
{
switch (parameter.Id)
{
case 1:
Index = new AirQualityIndexNumberElement(parameter, this);
return Index;
case 2:
Current = new AirPollutionImageElement(parameter, this);
return Current;

View File

@ -14,10 +14,11 @@ namespace WatchFace.Parser.Models.Elements
public long MinusImageIndex { get; set; }
public long? DegreesImageIndex { get; set; }
public void Draw(Graphics drawer, Bitmap[] resources, int temperature)
public void Draw(Graphics drawer, Bitmap[] resources, int temperature, CoordinatesElement altCoordinates = null)
{
var drawingBox = altCoordinates == null ? Number.GetBox() : Number.GetAltBox(altCoordinates);
var images = GetImagesForTemperature(resources, temperature);
DrawerHelper.DrawImages(drawer, images, (int) Number.Spacing, Number.Alignment, Number.GetBox());
DrawerHelper.DrawImages(drawer, images, (int) Number.Spacing, Number.Alignment, drawingBox);
}
public List<Bitmap> GetImagesForTemperature(Bitmap[] resources, int temperature)

View File

@ -1,31 +1,51 @@
namespace WatchFace.Parser.Models.Elements
using System.Drawing;
namespace WatchFace.Parser.Models.Elements
{
public class SeparateTemperatureElement : ContainerElement
{
public SeparateTemperatureElement(Parameter parameter, Element parent = null, string name = null) :
base(parameter, parent, name) { }
public TodayDayTemperatureElement Day { get; set; }
public TodayNightTemperatureElement Night { get; set; }
public CoordinatesElement Unknown3 { get; set; }
public CoordinatesElement Unknown4 { get; set; }
public TemperatureNumberElement Day { get; set; }
public TemperatureNumberElement Night { get; set; }
public CoordinatesElement DayAlt { get; set; }
public CoordinatesElement NightAlt { get; set; }
public override void Draw(Graphics drawer, Bitmap[] images, WatchState state)
{
if (state.CurrentTemperature != null)
{
if (state.DayTemperature != null)
Day?.Draw(drawer, images, state.DayTemperature.Value);
if (state.NightTemperature != null)
Night?.Draw(drawer, images, state.NightTemperature.Value);
}
else
{
if (state.DayTemperature != null)
Day?.Draw(drawer, images, state.DayTemperature.Value, DayAlt);
if (state.NightTemperature != null)
Night?.Draw(drawer, images, state.NightTemperature.Value, NightAlt);
}
}
protected override Element CreateChildForParameter(Parameter parameter)
{
switch (parameter.Id)
{
case 1:
Day = new TodayDayTemperatureElement(parameter, this);
Day = new TemperatureNumberElement(parameter, this);
return Day;
case 2:
Night = new TodayNightTemperatureElement(parameter, this);
Night = new TemperatureNumberElement(parameter, this);
return Night;
case 3:
Unknown3 = new CoordinatesElement(parameter, this);
return Unknown3;
DayAlt = new CoordinatesElement(parameter, this);
return DayAlt;
case 4:
Unknown4 = new CoordinatesElement(parameter, this);
return Unknown4;
NightAlt = new CoordinatesElement(parameter, this);
return NightAlt;
default:
return base.CreateChildForParameter(parameter);
}

View File

@ -1,17 +0,0 @@
using System.Drawing;
using WatchFace.Parser.Interfaces;
namespace WatchFace.Parser.Models.Elements
{
public class TodayDayTemperatureElement : TemperatureNumberElement, IDrawable
{
public TodayDayTemperatureElement(Parameter parameter, Element parent = null, string name = null) :
base(parameter, parent, name) { }
public void Draw(Graphics drawer, Bitmap[] resources, WatchState state)
{
if (state.DayTemperature != null)
Draw(drawer, resources, state.DayTemperature.Value);
}
}
}

View File

@ -10,17 +10,23 @@ namespace WatchFace.Parser.Models.Elements
base(parameter, parent, name) { }
public CoordinatesElement Current { get; set; }
public CoordinatesElement Today { get; set; }
public CoordinatesElement Tomorrow { get; set; }
public ImageSetElement CustomIcon { get; set; }
public CoordinatesElement CurrentAlt { get; set; }
public CoordinatesElement Unknown4 { get; set; }
public void Draw(Graphics drawer, Bitmap[] resources, WatchState state)
{
if (state.CurrentWeather != WeatherCondition.Unknown && Current != null)
drawer.DrawImage(LoadWeatherImage(state.CurrentWeather), Current.X, Current.Y);
else if (state.TodayWeather != WeatherCondition.Unknown && Today != null)
drawer.DrawImage(LoadWeatherImage(state.TodayWeather), Today.X, Today.Y);
else if (state.TomorrowWeather != WeatherCondition.Unknown && Tomorrow != null)
drawer.DrawImage(LoadWeatherImage(state.TomorrowWeather), Tomorrow.X, Tomorrow.Y);
var useAltCoordinates = CurrentAlt != null && state.CurrentTemperature == null;
var iconCoordinates = useAltCoordinates ? CurrentAlt : Current;
if (state.CurrentWeather > WeatherCondition.VeryHeavyDownpour ||
state.CurrentWeather < WeatherCondition.Unknown) return;
if (iconCoordinates != null)
drawer.DrawImage(LoadWeatherImage(state.CurrentWeather), iconCoordinates.X, iconCoordinates.Y);
if (CustomIcon != null)
drawer.DrawImage(resources[CustomIcon.ImageIndex + (int)state.CurrentWeather], CustomIcon.X, CustomIcon.Y);
}
private static Bitmap LoadWeatherImage(WeatherCondition weather)
@ -37,12 +43,15 @@ namespace WatchFace.Parser.Models.Elements
case 1:
Current = new CoordinatesElement(parameter, this);
return Current;
case 2:
CustomIcon = new ImageSetElement(parameter, this);
return CustomIcon;
case 3:
Today = new CoordinatesElement(parameter, this);
return Today;
CurrentAlt = new CoordinatesElement(parameter, this);
return CurrentAlt;
case 4:
Tomorrow = new CoordinatesElement(parameter, this);
return Tomorrow;
Unknown4 = new CoordinatesElement(parameter, this);
return Unknown4;
default:
return base.CreateChildForParameter(parameter);
}

View File

@ -1,4 +1,6 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace WatchFace.Parser.Models
{
@ -12,15 +14,19 @@ namespace WatchFace.Parser.Models
public int Distance { get; set; } = 2367;
public int? Pulse { get; set; } = 62;
[JsonConverter(typeof(StringEnumConverter))]
public WeatherCondition CurrentWeather { get; set; } = WeatherCondition.PartlyCloudy;
public int? CurrentTemperature { get; set; } = -10;
public int? DayTemperature { get; set; } = -15;
public int? NightTemperature { get; set; } = -24;
public int? TomorrowDayTemperature { get; set; }
public int? TomorrowNightTemperature { get; set; }
public WeatherCondition CurrentWeather { get; set; } = WeatherCondition.Cloudy;
public WeatherCondition TodayWeather { get; set; } = WeatherCondition.Unknown;
public WeatherCondition TomorrowWeather { get; set; } = WeatherCondition.Unknown;
public AirCondition Air { get; set; } = AirCondition.Excellent;
// https://en.wikipedia.org/wiki/Air_quality_index#Mainland_China
[JsonConverter(typeof(StringEnumConverter))]
public AirCondition AirQuality { get; set; } = AirCondition.Excellent;
public int? AirQualityIndex { get; set; } = 15;
public int BatteryLevel { get; set; } = 67;
public bool Bluetooth { get; set; } = true;

View File

@ -2,7 +2,31 @@
{
public enum WeatherCondition
{
Unknown = 279,
Cloudy = 247
Unknown,
PartlyCloudy,
CloudyAndRain,
CloudyAndSnow,
Sunny,
Cloudy,
LightRain,
LightSnow,
Rain,
Snow,
HeavySnow,
HeavyRain,
SandStorm,
SnowAndRain,
Fog,
Haze,
Storm,
VeryHeavySnow,
FloatingDust,
Downpour,
Hail,
HailStorm,
HeavyDownpour,
BlowingDust,
Tornado,
VeryHeavyDownpour
}
}

View File

@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using BumpKit;
using WatchFace.Parser.Interfaces;
using WatchFace.Parser.Models;
@ -10,46 +7,23 @@ namespace WatchFace.Parser
{
public class PreviewGenerator
{
public static void CreateGif(List<Parameter> descriptor, Bitmap[] images, Stream outputStream)
public static IEnumerable<Image> CreateAnimation(List<Parameter> descriptor, Bitmap[] images,
IEnumerable<WatchState> states)
{
var previewWatchFace = new Models.Elements.WatchFace(descriptor);
var watchState = new WatchState();
var time = watchState.Time;
using (var encoder = new GifEncoder(outputStream))
foreach (var watchState in states)
{
for (var i = 0; i < 10; i++)
using (var image = CreateFrame(previewWatchFace, images, watchState))
{
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));
}
yield return image;
}
}
}
public static Image CreateImage(IEnumerable<Parameter> descriptor, Bitmap[] resources)
public static Image CreateImage(IEnumerable<Parameter> descriptor, Bitmap[] images, WatchState state)
{
var previewWatchFace = new Models.Elements.WatchFace(descriptor);
return CreateFrame(previewWatchFace, resources, new WatchState());
return CreateFrame(previewWatchFace, images, state);
}
private static Image CreateFrame(IDrawable watchFace, Bitmap[] resources, WatchState state)

View File

@ -1,8 +1,12 @@
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using NLog;
using Resources.Models;
using WatchFace.Parser.Models;
using Header = WatchFace.Parser.Models.Header;
using Image = Resources.Models.Image;
namespace WatchFace.Parser
{
@ -18,7 +22,8 @@ namespace WatchFace.Parser
}
public List<Parameter> Parameters { get; private set; }
public List<Bitmap> Images { get; private set; }
public List<IResource> Resources { get; private set; }
public Bitmap[] Images => Resources.OfType<Image>().Select(i => i.Bitmap).ToArray();
public void Read()
{
@ -46,7 +51,7 @@ namespace WatchFace.Parser
Logger.Trace("Watch face parameters locations were read:");
Parameters = ReadParameters(parametrsTableLength, parametersLocations);
Images = new Resources.Reader(_stream).Read((uint) imagesCount);
Resources = new Resources.Reader(_stream).Read((uint) imagesCount);
}
private List<Parameter> ReadParameters(long coordinatesTableSize, ICollection<Parameter> parametersDescriptors)

View File

@ -0,0 +1,19 @@
using System.Collections.Generic;
using WatchFace.Parser.Models;
namespace WatchFace.Parser.Utils
{
public class DrawingOrderIterator
{
public static IEnumerable<DrawingOrderPosition> Iterate(long drawingOrder)
{
var order = drawingOrder;
while (order != 0)
{
var position = (DrawingOrderPosition) ((order & 0xf000) >> 12);
yield return position;
order = (order << 4) & 0xffff;
}
}
}
}

View File

@ -15,8 +15,6 @@ namespace WatchFace.Parser.Utils
var result = new List<Parameter>();
var currentType = typeof(T);
if (!string.IsNullOrEmpty(path))
Logger.Trace("{0} '{1}'", path, currentType.Name);
foreach (var kv in ElementsHelper.SortedProperties<T>())
{
var id = kv.Key;
@ -52,11 +50,21 @@ namespace WatchFace.Parser.Utils
else if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>))
{
foreach (var item in propertyValue)
{
Logger.Trace("{0} '{1}'", currentPath, propertyInfo.Name);
result.Add(new Parameter(id, Build(item, currentPath)));
}
}
else
{
result.Add(new Parameter(id, Build(propertyValue, currentPath)));
var innerParameters = Build(propertyValue, currentPath);
if (innerParameters.Count > 0)
{
Logger.Trace("{0} '{1}'", currentPath, propertyInfo.Name);
result.Add(new Parameter(id, innerParameters));
}
else
Logger.Trace("{0} '{1}': Skipped because of empty", currentPath, propertyInfo.Name);
}
}
@ -71,8 +79,6 @@ namespace WatchFace.Parser.Utils
var thisMethod = typeof(ParametersConverter).GetMethod(nameof(Parse));
if (!string.IsNullOrEmpty(path))
Logger.Trace("{0} '{1}'", path, currentType.Name);
foreach (var parameter in descriptor)
{
var currentPath = string.IsNullOrEmpty(path)
@ -107,6 +113,7 @@ namespace WatchFace.Parser.Utils
}
else if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(List<>))
{
Logger.Trace("{0} '{1}'", currentPath, propertyInfo.Name);
dynamic propertyValue = propertyInfo.GetValue(result, null);
if (propertyValue == null)
{
@ -128,6 +135,7 @@ namespace WatchFace.Parser.Utils
}
else
{
Logger.Trace("{0} '{1}'", currentPath, propertyInfo.Name);
dynamic propertyValue = propertyInfo.GetValue(result, null);
if (propertyValue != null)
throw new ArgumentException($"Parameter {parameter.Id} is already set for {currentType.Name}");

View File

@ -1,31 +1,35 @@
using System;
using NLog;
using Resources;
using System;
using System.Collections.Generic;
using System.Drawing;
using NLog;
using Resources;
using System.Linq;
using Resources.Models;
using WatchFace.Parser.Attributes;
using Image = Resources.Models.Image;
namespace WatchFace.Parser.Utils
{
public class ImagesLoader
public class ResourcesLoader
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly string _imagesDirectory;
private readonly Dictionary<long, long> _mapping;
public ImagesLoader(string imagesDirectory)
public ResourcesLoader(string imagesDirectory)
{
Images = new List<Bitmap>();
Resources = new List<IResource>();
_mapping = new Dictionary<long, long>();
_imagesDirectory = imagesDirectory;
}
public List<Bitmap> Images { get; }
public List<IResource> Resources { get; }
public Bitmap[] Images => Resources.OfType<Image>().Select(i => i.Bitmap).ToArray();
public void Process<T>(T serializable, string path = "")
{
if (!string.IsNullOrEmpty(path)) Logger.Trace("Loading images for {0} '{1}'", path, typeof(T).Name);
if (!string.IsNullOrEmpty(path)) Logger.Trace("Loading resources for {0} '{1}'", path, typeof(T).Name);
long? lastImageIndexValue = null;
@ -67,7 +71,7 @@ namespace WatchFace.Parser.Utils
{
if (lastImageIndexValue == null)
throw new ArgumentException(
$"Property {propertyInfo.Name} can't be processed becuase ImageIndex isn't present or it is zero"
$"Property {propertyInfo.Name} can't be processed because ImageIndex isn't present or it is zero"
);
var imagesCount = propertyType.IsGenericType
@ -102,9 +106,10 @@ namespace WatchFace.Parser.Utils
if (_mapping.ContainsKey(index))
return _mapping[index];
var newImageIndex = Images.Count;
var newImageIndex = Resources.Count;
Logger.Trace("Loading image {0}...", newImageIndex);
Images.Add(ImageLoader.LoadImageForNumber(_imagesDirectory, index));
var resource = ImageLoader.LoadResourceForNumber(_imagesDirectory, index);
Resources.Add(resource);
_mapping[index] = newImageIndex;
return newImageIndex;
}

View File

@ -31,19 +31,19 @@
<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 Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.11.0.2\lib\net40\Newtonsoft.Json.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>
<HintPath>..\packages\NLog.4.5.6\lib\net40-client\NLog.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
@ -59,20 +59,25 @@
<Compile Include="Elements\BasicElements\Coordinates.cs" />
<Compile Include="Elements\BasicElements\Image.cs" />
<Compile Include="Elements\BasicElements\ImageSet.cs" />
<Compile Include="Elements\BasicElements\UnknownType14d6.cs" />
<Compile Include="Elements\BasicElements\UnknownType.cs" />
<Compile Include="Elements\BasicElements\Number.cs" />
<Compile Include="Elements\BasicElements\Scale.cs" />
<Compile Include="Elements\Battery.cs" />
<Compile Include="Elements\Date.cs" />
<Compile Include="Elements\DateElements\DateUnknown3.cs" />
<Compile Include="Elements\DateElements\MonthAndDay.cs" />
<Compile Include="Elements\DateElements\OneLineMonthAndDay.cs" />
<Compile Include="Elements\DateElements\SeparateMonthAndDay.cs" />
<Compile Include="Elements\StepsProgress.cs" />
<Compile Include="Elements\Status.cs" />
<Compile Include="Elements\StatusElements\Switch.cs" />
<Compile Include="Elements\UnknownType14.cs" />
<Compile Include="Elements\Time.cs" />
<Compile Include="Elements\TimeElements\AmPm.cs" />
<Compile Include="Elements\TimeElements\TwoDigits.cs" />
<Compile Include="Elements\Weather.cs" />
<Compile Include="Elements\WeatherElements\CustomWeatherIcon.cs" />
<Compile Include="Elements\WeatherElements\AirPollution.cs" />
<Compile Include="Elements\WeatherElements\OneLineTemperature.cs" />
<Compile Include="Elements\WeatherElements\SeparateTemperature.cs" />
@ -82,7 +87,9 @@
<Compile Include="Elements\WeatherElements\WeatherIcon.cs" />
<Compile Include="Helpers\DrawerHelper.cs" />
<Compile Include="Interfaces\IDrawable.cs" />
<Compile Include="JsonConverters\HexStringJsonConverter.cs" />
<Compile Include="JsonConverters\DrawingOrderJsonConverter.cs" />
<Compile Include="JsonConverters\ColorJsonConverter.cs" />
<Compile Include="Models\DrawingOrderPosition.cs" />
<Compile Include="Models\Elements\Activity\CaloriesElement.cs" />
<Compile Include="Models\Elements\Activity\StepsElement.cs" />
<Compile Include="Models\Elements\Activity\StepsGoalElement.cs" />
@ -115,14 +122,10 @@
<Compile Include="Models\Elements\Date\MonthAndDayElement.cs" />
<Compile Include="Models\Elements\Date\WeekDayElement.cs" />
<Compile Include="Models\Elements\TimeElement.cs" />
<Compile Include="Models\Elements\Time\SecondsElement.cs" />
<Compile Include="Models\Elements\Time\MinutesElement.cs" />
<Compile Include="Models\Elements\Time\HoursElement.cs" />
<Compile Include="Models\Elements\Common\TwoDigitsElement.cs" />
<Compile Include="Models\Elements\WatchFace.cs" />
<Compile Include="Models\Elements\Weather\AirPollution\AirQualityIndexNumberElement.cs" />
<Compile Include="Models\Elements\Weather\Temperature\Today\OnelineTemperatureElement.cs" />
<Compile Include="Models\Elements\Weather\Temperature\Today\TodayNightTemperatureElement.cs" />
<Compile Include="Models\Elements\Weather\Temperature\Today\TodayDayTemperatureElement.cs" />
<Compile Include="Models\Elements\Weather\Temperature\CurrentTemperatureElement.cs" />
<Compile Include="Models\Elements\Weather\AirPollution\AirPollutionImageElement.cs" />
<Compile Include="Models\Elements\Weather\AirPollutionElement.cs" />
@ -155,8 +158,9 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Reader.cs" />
<Compile Include="Attributes\ParameterImagesCountAttribute.cs" />
<Compile Include="Utils\DrawingOrderIterator.cs" />
<Compile Include="Utils\ElementsHelper.cs" />
<Compile Include="Utils\ImagesLoader.cs" />
<Compile Include="Utils\ResourcesLoader.cs" />
<Compile Include="Utils\ParametersConverter.cs" />
<Compile Include="Attributes\ParameterImageIndexAttribute.cs" />
<Compile Include="Attributes\ParameterIdAttribute.cs" />
@ -173,13 +177,34 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="WeatherIcons\247.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="WeatherIcons\279.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Include="WeatherIcons\1.png" />
<EmbeddedResource Include="WeatherIcons\0.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="WeatherIcons\10.png" />
<EmbeddedResource Include="WeatherIcons\11.png" />
<EmbeddedResource Include="WeatherIcons\12.png" />
<EmbeddedResource Include="WeatherIcons\13.png" />
<EmbeddedResource Include="WeatherIcons\14.png" />
<EmbeddedResource Include="WeatherIcons\15.png" />
<EmbeddedResource Include="WeatherIcons\16.png" />
<EmbeddedResource Include="WeatherIcons\17.png" />
<EmbeddedResource Include="WeatherIcons\18.png" />
<EmbeddedResource Include="WeatherIcons\19.png" />
<EmbeddedResource Include="WeatherIcons\2.png" />
<EmbeddedResource Include="WeatherIcons\20.png" />
<EmbeddedResource Include="WeatherIcons\21.png" />
<EmbeddedResource Include="WeatherIcons\22.png" />
<EmbeddedResource Include="WeatherIcons\23.png" />
<EmbeddedResource Include="WeatherIcons\24.png" />
<EmbeddedResource Include="WeatherIcons\25.png" />
<EmbeddedResource Include="WeatherIcons\3.png" />
<EmbeddedResource Include="WeatherIcons\4.png" />
<EmbeddedResource Include="WeatherIcons\5.png" />
<EmbeddedResource Include="WeatherIcons\6.png" />
<EmbeddedResource Include="WeatherIcons\7.png" />
<EmbeddedResource Include="WeatherIcons\8.png" />
<EmbeddedResource Include="WeatherIcons\9.png" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp60</s:String></wpf:ResourceDictionary>

View File

@ -31,5 +31,8 @@ namespace WatchFace.Parser
[ParameterId(10)]
public AnalogDialFace AnalogDialFace { get; set; }
[ParameterId(14)]
public UnknownType14 Unknown14 { get; set; }
}
}

View File

Before

Width:  |  Height:  |  Size: 332 B

After

Width:  |  Height:  |  Size: 332 B

View File

Before

Width:  |  Height:  |  Size: 352 B

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 B

View File

@ -2,19 +2,20 @@
using System.Drawing;
using System.IO;
using NLog;
using Resources.Models;
using WatchFace.Parser.Models;
using WatchFace.Parser.Utils;
using Header = WatchFace.Parser.Models.Header;
namespace WatchFace.Parser
{
public class Writer
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly List<Bitmap> _images;
private readonly List<IResource> _images;
private readonly Stream _stream;
public Writer(Stream stream, List<Bitmap> images)
public Writer(Stream stream, List<IResource> images)
{
_stream = stream;
_images = images;
@ -26,6 +27,7 @@ namespace WatchFace.Parser
var encodedParameters = new Dictionary<byte, MemoryStream>(descriptor.Count);
foreach (var parameter in descriptor)
{
Logger.Trace("Parameter: {0}", parameter.Id);
var memoryStream = new MemoryStream();
foreach (var child in parameter.Children)
child.Write(memoryStream);

View File

@ -1,6 +1,5 @@
<?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" />
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net40-client" />
<package id="NLog" version="4.5.6" targetFramework="net40-client" />
</packages>

View File

@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
using BumpKit;
using Newtonsoft.Json;
using NLog;
using NLog.Config;
@ -10,7 +13,9 @@ using NLog.Targets;
using Resources;
using Resources.Models;
using WatchFace.Parser;
using WatchFace.Parser.Models;
using WatchFace.Parser.Utils;
using Image = System.Drawing.Image;
using Reader = WatchFace.Parser.Reader;
using Writer = WatchFace.Parser.Writer;
@ -20,6 +25,7 @@ namespace WatchFace
{
private const string AppName = "WatchFace";
private static readonly bool IsRunningOnMono = Type.GetType("Mono.Runtime") != null;
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private static void Main(string[] args)
@ -50,6 +56,7 @@ namespace WatchFace
Console.WriteLine("File or directory '{0}' doesn't exists.", inputFileName);
continue;
}
if (isDirectory)
{
Console.WriteLine("Processing directory '{0}'", inputFileName);
@ -61,6 +68,7 @@ namespace WatchFace
{
Logger.Fatal(e);
}
continue;
}
@ -99,13 +107,13 @@ namespace WatchFace
var outputFileName = Path.Combine(outputDirectory, baseName + "_packed.bin");
SetupLogger(Path.ChangeExtension(outputFileName, ".log"));
var watchFace = ReadConfig(inputFileName);
var watchFace = ReadWatchFaceConfig(inputFileName);
if (watchFace == null) return;
var imagesDirectory = Path.GetDirectoryName(inputFileName);
try
{
WriteWatchFace(outputFileName, imagesDirectory, watchFace);
WriteWatchFace(outputDirectory, outputFileName, imagesDirectory, watchFace);
}
catch (Exception)
{
@ -126,18 +134,12 @@ namespace WatchFace
var watchFace = ParseResources(reader);
if (watchFace == null) return;
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);
}
GeneratePreviews(reader.Parameters, reader.Images, outputDirectory, baseName);
Logger.Debug("Exporting resources to '{0}'", outputDirectory);
var reDescriptor = new FileDescriptor {Images = reader.Images};
var reDescriptor = new FileDescriptor {Resources = reader.Resources};
new Extractor(reDescriptor).Extract(outputDirectory);
ExportConfig(watchFace, Path.Combine(outputDirectory, $"{baseName}.json"));
ExportWatchFaceConfig(watchFace, Path.Combine(outputDirectory, $"{baseName}.json"));
}
private static void PackResources(string inputDirectory)
@ -148,30 +150,52 @@ namespace WatchFace
var logFileName = Path.Combine(outputDirectory, $"{baseName}_packed.log");
SetupLogger(logFileName);
FileDescriptor resDescriptor;
var headerFileName = Path.Combine(inputDirectory, "header.json");
var versionFileName = Path.Combine(inputDirectory, "version");
var resDescriptor = new FileDescriptor();
using (var stream = File.OpenRead(versionFileName))
using (var reader = new BinaryReader(stream))
if (File.Exists(headerFileName))
{
resDescriptor.Version = reader.ReadByte();
resDescriptor = ReadResConfig(headerFileName);
}
else if (File.Exists(versionFileName))
{
resDescriptor = new FileDescriptor();
using (var stream = File.OpenRead(versionFileName))
using (var reader = new BinaryReader(stream))
{
resDescriptor.Version = reader.ReadByte();
}
}
else
{
throw new ArgumentException(
"File 'header.json' or 'version' should exists in the folder with unpacked images. Res-file couldn't be created"
);
}
var i = 0;
var images = new List<Bitmap>();
while (true)
var images = new List<IResource>();
while (resDescriptor.ResourcesCount == null || i < resDescriptor.ResourcesCount.Value)
{
try
{
var image = ImageLoader.LoadImageForNumber(inputDirectory, i);
images.Add(image);
var resource = ImageLoader.LoadResourceForNumber(inputDirectory, i);
images.Add(resource);
}
catch (FileNotFoundException)
{
Logger.Info("All images with sequenced names are loaded. Latest loaded image: {0}", i - 1);
break;
}
i++;
}
resDescriptor.Images = images;
if (resDescriptor.ResourcesCount != null && resDescriptor.ResourcesCount.Value != images.Count)
throw new ArgumentException(
$"The .res-file should contain {resDescriptor.ResourcesCount.Value} images but was loaded {images.Count} images.");
resDescriptor.Resources = images;
using (var stream = File.OpenWrite(outputFileName))
{
@ -191,32 +215,28 @@ namespace WatchFace
resDescriptor = FileReader.Read(stream);
}
ExportResConfig(resDescriptor, Path.Combine(outputDirectory, "header.json"));
new Extractor(resDescriptor).Extract(outputDirectory);
}
private static void WriteWatchFace(string outputFileName, string imagesDirectory, Parser.WatchFace watchFace)
private static void WriteWatchFace(string outputDirectory, string outputFileName, string imagesDirectory, Parser.WatchFace watchFace)
{
try
{
Logger.Debug("Reading referenced images from '{0}'", imagesDirectory);
var imagesReader = new ImagesLoader(imagesDirectory);
var imagesReader = new ResourcesLoader(imagesDirectory);
imagesReader.Process(watchFace);
Logger.Trace("Building parameters for watch face...");
var descriptor = ParametersConverter.Build(watchFace);
Logger.Debug("Generating preview...");
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);
}
var baseFilename = Path.GetFileNameWithoutExtension(outputFileName);
GeneratePreviews(descriptor, imagesReader.Images, outputDirectory, baseFilename);
Logger.Debug("Writing watch face to '{0}'", outputFileName);
using (var fileStream = File.OpenWrite(outputFileName))
{
var writer = new Writer(fileStream, imagesReader.Images);
var writer = new Writer(fileStream, imagesReader.Resources);
writer.Write(descriptor);
fileStream.Flush();
}
@ -271,7 +291,7 @@ namespace WatchFace
return unpackedPath;
}
private static Parser.WatchFace ReadConfig(string jsonFileName)
private static Parser.WatchFace ReadWatchFaceConfig(string jsonFileName)
{
Logger.Debug("Reading config...");
try
@ -294,7 +314,30 @@ namespace WatchFace
}
}
private static void ExportConfig(Parser.WatchFace watchFace, string jsonFileName)
private static FileDescriptor ReadResConfig(string jsonFileName)
{
Logger.Debug("Reading resources config...");
try
{
using (var fileStream = File.OpenRead(jsonFileName))
using (var reader = new StreamReader(fileStream))
{
return JsonConvert.DeserializeObject<FileDescriptor>(reader.ReadToEnd(),
new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Error,
NullValueHandling = NullValueHandling.Ignore
});
}
}
catch (Exception e)
{
Logger.Fatal(e);
return null;
}
}
private static void ExportWatchFaceConfig(Parser.WatchFace watchFace, string jsonFileName)
{
Logger.Debug("Exporting config...");
try
@ -313,6 +356,25 @@ namespace WatchFace
}
}
private static void ExportResConfig(FileDescriptor resDescriptor, string jsonFileName)
{
Logger.Debug("Exporting resources config...");
try
{
using (var fileStream = File.OpenWrite(jsonFileName))
using (var writer = new StreamWriter(fileStream))
{
writer.Write(JsonConvert.SerializeObject(resDescriptor, Formatting.Indented,
new JsonSerializerSettings {NullValueHandling = NullValueHandling.Ignore}));
writer.Flush();
}
}
catch (Exception e)
{
Logger.Fatal(e);
}
}
private static void SetupLogger(string logFileName)
{
var config = new LoggingConfiguration();
@ -320,7 +382,10 @@ namespace WatchFace
var fileTarget = new FileTarget
{
FileName = logFileName,
Layout = "${level}|${message}"
Layout = "${level}|${message}",
KeepFileOpen = true,
ConcurrentWrites = false,
OpenFileCacheTimeout = 30
};
config.AddTarget("file", fileTarget);
config.LoggingRules.Add(new LoggingRule("*", LogLevel.Trace, fileTarget));
@ -331,5 +396,109 @@ namespace WatchFace
LogManager.Configuration = config;
}
private static void GeneratePreviews(List<Parameter> parameters, Bitmap[] images, string outputDirectory, string baseName)
{
Logger.Debug("Generating previews...");
var states = GetPreviewStates();
var staticPreview = PreviewGenerator.CreateImage(parameters, images, new WatchState());
staticPreview.Save(Path.Combine(outputDirectory, $"{baseName}_static.png"), ImageFormat.Png);
var previewImages = PreviewGenerator.CreateAnimation(parameters, images, states);
if (IsRunningOnMono)
{
var i = 0;
foreach (var previewImage in previewImages)
{
previewImage.Save(Path.Combine(outputDirectory, $"{baseName}_animated_{i}.png"), ImageFormat.Png);
i++;
}
}
else
{
using (var gifOutput = File.OpenWrite(Path.Combine(outputDirectory, $"{baseName}_animated.gif")))
using (var encoder = new GifEncoder(gifOutput))
{
foreach (var previewImage in previewImages)
encoder.AddFrame(previewImage, frameDelay: TimeSpan.FromSeconds(1));
}
}
}
private static IEnumerable<WatchState> GetPreviewStates()
{
var appPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var previewStatesPath = Path.Combine(appPath, "PreviewStates.json");
if (File.Exists(previewStatesPath))
using (var stream = File.OpenRead(previewStatesPath))
using (var reader = new StreamReader(stream))
{
var json = reader.ReadToEnd();
return JsonConvert.DeserializeObject<List<WatchState>>(json);
}
var previewStates = GenerateSampleStates();
using (var stream = File.OpenWrite(previewStatesPath))
using (var writer = new StreamWriter(stream))
{
var json = JsonConvert.SerializeObject(previewStates, Formatting.Indented);
writer.Write(json);
writer.Flush();
}
return previewStates;
}
private static IEnumerable<WatchState> GenerateSampleStates()
{
var time = DateTime.Now;
var states = new List<WatchState>();
for (var i = 0; i < 10; i++)
{
var num = i + 1;
var watchState = new WatchState
{
BatteryLevel = 100 - i * 10,
Pulse = 60 + num * 2,
Steps = num * 1000,
Calories = num * 75,
Distance = num * 700,
Bluetooth = num > 1 && num < 6,
Unlocked = num > 2 && num < 7,
Alarm = num > 3 && num < 8,
DoNotDisturb = num > 4 && num < 9,
DayTemperature = -15 + 2 * i,
NightTemperature = -24 + i * 4,
};
if (num < 3)
{
watchState.AirQuality = AirCondition.Unknown;
watchState.AirQualityIndex = null;
watchState.CurrentWeather = WeatherCondition.Unknown;
watchState.CurrentTemperature = null;
}
else
{
var index = num - 2;
watchState.AirQuality = (AirCondition) index;
watchState.CurrentWeather = (WeatherCondition) index;
watchState.AirQualityIndex = index * 50 - 25;
watchState.CurrentTemperature = -10 + i * 6;
}
watchState.Time = new DateTime(time.Year, num, num * 2 + 5, i * 2, i * 6, i);
states.Add(watchState);
}
return states;
}
}
}

View File

@ -35,16 +35,24 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<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 Include="BumpKit, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Bumpkit.1.0.2\lib\BumpKit.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.11.0.2\lib\net40\Newtonsoft.Json.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>
<HintPath>..\packages\NLog.4.5.6\lib\net40-client\NLog.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Numerics" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />

View File

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp60</s:String></wpf:ResourceDictionary>

View File

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