I am learning C# and have been taking a lot of online courses.
I am looking for a simpler/neater way to enumerate a list within a list.
In python we can do something like this in just one line:
newListofList=[[n,i] for n,i in enumerate([List1,List2,List3])]
Does it have to involve lambda and Linq in C#? if so, what would be the solution? I tried it with Dictionary in C# but my gut tells me this is not a perfect solution.
List<List<string>> familyListss = new List<List<string>>();
familyListss.Add(new List<string> { "Mary", "Mary_sister", "Mary_father", "Mary_mother", "Mary_brother" });
familyListss.Add(new List<string> { "Peter", "Peter_sister", "Peter_father", "Peter_mother", "Peter_brother" });
familyListss.Add(new List<string> { "John", "John_sister", "John_father", "John_mother", "John_brother" });
Dictionary<int, List<string>> familyData = new Dictionary<int, List<string>>();
for (int i = 0; i < familyListss.Count; i++)
{
familyData.Add(i, familyListss[i]);
}
Just a constructor will be enough:
List<List<string>> familyListss = new List<List<string>>() {
new List<string> { "Mary", "Mary_sister", "Mary_father", "Mary_mother", "Mary_brother" },
new List<string> { "Peter", "Peter_sister", "Peter_father", "Peter_mother", "Peter_brother" },
new List<string> { "John", "John_sister", "John_father", "John_mother", "John_brother" }
};
If you want to mimic enumerate you can use Linq, Select((value, index) => your lambda here):
using System.Linq;
...
var list = new List<string>() {
"a", "b", "c", "d"};
var result = list
.Select((value, index) => $"item[{index}] = {value}");
Console.Write(string.Join(Environment.NewLine, result));
Outcome:
item[0] = a
item[1] = b
item[2] = c
item[3] = d
Are you taking about something like this?
int i = 0;
familyListss.ForEach(f => { familyData.Add(i, f);i++; });
This is refactored from
int i = 0;
foreach (var f in familyListss)
{
familyData.Add(i, f);
i++;
}
With a small extension method, you can build in an index to foreach to make it one line. Extension methods are worth exploring, and can take annoying, repeated tasks out of your way.
Also see this question:
C# Convert List<string> to Dictionary<string, string>
I am trying to convert this Python code to C# (.NET Core ideally).
Source
My goal is to convert the QR input string to another string containing the data in json. See the provided links.
#! /usr/bin/env python3
import json
import sys
import zlib
import base45
import cbor2
from cose.messages import CoseMessage
payload = sys.argv[1][4:]
print("decoding payload: "+ payload)
# decode Base45 (remove HC1: prefix)
decoded = base45.b45decode(payload)
# decompress using zlib
decompressed = zlib.decompress(decoded)
# decode COSE message (no signature verification done)
cose = CoseMessage.decode(decompressed)
# decode the CBOR encoded payload and print as json
print(json.dumps(cbor2.loads(cose.payload), indent=2))
I couldn't find any NuGet package for Zlib, that would work correctly. So I am stuck straight after base45 decoding. Thanks for any tips.
using System.Text; //Rystem.Text.Base45 NuGet
var removedHeader = testQrData.Substring(4);
var decoded = removedHeader.FromBase45();
byte[] rawBytes = Encoding.ASCII.GetBytes(decoded);
This link might be helpful for further investigation.
Decoding scheme
IBarcodeReader reader = new BarcodeReader();//using Zxing
var barcodeBitmap = (Bitmap)Bitmap.FromFile("qrcode.png");
var barcodeReader = new BarcodeReader();
var qrcontent = barcodeReader.Decode(barcodeBitmap).Text;
var qrmessage = qrcontent.Substring(4);//remove first 4 chars
byte[] decodedBase45 = Base45Encoding.Decode(qrmessage);//using base45 lib
var cose = ZlibStream.UncompressBuffer(decodedBase45);//using zlib or similar
var decrypted = Message.DecodeFromBytes(cose).GetContent(); //using COSE
CBORObject cbor = CBORObject.DecodeFromBytes(decrypted); //using Peter.O.. CBOR
var jsonDecoded = cbor.ToJSONString(); //or deserialize it to custom class
I added "Zlib.Portable" from NuGet and used "ZlibStream.UncompressString"
byte[] decoded = Base45Encoding.Decode(input);
var stringResult = ZlibStream.UncompressString(decoded);`
I'm stuck in the next step "CoseMessage.decode" :/
I have used the ZXing library in the past to decode and create QR Codes:
https://github.com/micjahn/ZXing.Net
You could also try: https://github.com/codebude/QRCoder
A quick ZXing example from their github page:
// create a barcode reader instance
IBarcodeReader reader = new BarcodeReader();
// load a bitmap
var barcodeBitmap = (Bitmap)Image.LoadFrom("C:\\sample-barcode-image.png");
// detect and decode the barcode inside the bitmap
var result = reader.Decode(barcodeBitmap);
// do something with the result
if (result != null)
{
txtDecoderType.Text = result.BarcodeFormat.ToString();
txtDecoderContent.Text = result.Text;
}
This example reads a QR Code image.
I'm not sure what your input is but i would assume it's also an image in binary format so you might have to play around to get it working.
This solution works for me.
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using System;
using System.IO;
using Com.AugustCellars.COSE;
using PeterO.Cbor;
using Newtonsoft.Json;
using Microsoft.Extensions.Logging;
using System.Linq;
using System.Collections.Generic;
namespace DGCVerification.Services {
public class DGCVerificationService: IDGCVerificationService {
private readonly string _certificatePrefix = "HC1:";
private readonly ILogger < DGCVerificationService > _logger;
public DGCVerificationService(ILogger < DGCVerificationService > logger) {
_logger = logger;
}
private bool TryTrimPrefix(string certificateString, out string result) {
if (!certificateString.StartsWith(_certificatePrefix)) {
result = null;
return false;
}
result = certificateString.Substring(_certificatePrefix.Length);
return true;
}
private byte[] DecompressFromZlib(byte[] input) {
using(var memoryStream = new MemoryStream(input)) {
using(var decompressionStream = new InflaterInputStream(memoryStream)) {
using(var resultStream = new MemoryStream()) {
decompressionStream.CopyTo(resultStream);
var result = resultStream.ToArray();
return result;
}
}
}
}
private bool VerifyCoseSignature(Sign1Message coseMessage, string signature) {
var signatureAsBytes = Convert.FromBase64String(signature);
var publicKey = OneKey.FromX509(signatureAsBytes);
var result = coseMessage.Validate(publicKey);
_logger.LogInformation($"cose message signature {signature} verified as {result}");
return result;
}
private DGCPayload DecodeAndDeserialize(string certificateString, out Sign1Message coseMessage) {
coseMessage = null;
if (!TryTrimPrefix(certificateString, out
var trimmedPrefixString)) {
_logger.LogInformation($"certificate {certificateString} didn't have proper prefix {_certificatePrefix}");
return null;
}
var bytesBase256 = Base45Encoding.Decode(trimmedPrefixString);
_logger.LogInformation($"certificate {certificateString} base45 decoded to {Convert.ToBase64String(bytesBase256)}");
var decompressedFromZlib = DecompressFromZlib(bytesBase256);
_logger.LogDebug($"certificate {certificateString} zlib decompressed to {Convert.ToBase64String(decompressedFromZlib)}");
coseMessage = Message.DecodeFromBytes(decompressedFromZlib) as Sign1Message;
var coseMessagePayload = coseMessage.GetContent();
var cborResult = CBORObject.DecodeFromBytes(coseMessagePayload);
var jsonResult = cborResult.ToJSONString();
var result = JsonConvert.DeserializeObject < DGCPayloadCzechVersionRoot > (jsonResult);
return result.DGCPayloadWrap.DGCPayload;
}
private bool IsNotExpiredDGC(DGCPayload dGCPayload, UzisData uzisData) {
var vaccine = dGCPayload.Vaccination?.FirstOrDefault();
if (vaccine != null) {
var vaccinationValidityRules = uzisData.DGCValidityCzechRules.Rules
.FirstOrDefault()
.PlatnostiVakcinace;
var rule = vaccinationValidityRules.FirstOrDefault(x => x.VaccineMedicinalProduct == vaccine.Mp) ??
vaccinationValidityRules.FirstOrDefault(x => x.VaccineMedicinalProduct == null);
if (!DateTime.TryParse(vaccine.Dt, out
var vaccinatedOnDate)) {
_logger.LogError($"couldn't parse date of vaccination for DGC : {JsonConvert.SerializeObject(dGCPayload)}");
return false;
}
var result = DateTime.Now.Date <= vaccinatedOnDate.AddMonths(rule.OdolnostMesicDo);
return result;
}
var test = dGCPayload.Test?.FirstOrDefault();
if (test != null) {
var testValidityRule = uzisData.DGCValidityCzechRules.Rules
.FirstOrDefault()
.PlatnostiTestu
.FirstOrDefault(x => x.TypeOfTest == test.Tt);
if (!DateTime.TryParse(test.Tr, out
var testedOnDate)) {
_logger.LogError($"couldn't parse date of test for DGC : {JsonConvert.SerializeObject(dGCPayload)}");
return false;
}
var result = DateTime.Now.Date <= testedOnDate.AddHours(testValidityRule.PlatnostHod);
return result;
}
var recovery = dGCPayload.Recovery?.FirstOrDefault();
if (recovery != null) {
if (!DateTime.TryParse(recovery.Du, out
var recoveryValidUntil)) {
_logger.LogError($"couldn't parse recovert valid until for DGC : {JsonConvert.SerializeObject(dGCPayload)}");
return false;
}
var result = DateTime.Now.Date <= recoveryValidUntil;
return result;
}
return false;
}
private string GetCountryFromDGC(DGCPayload dGCPayload) {
var result = dGCPayload.Vaccination?.FirstOrDefault()?.Co ??
dGCPayload.Test?.FirstOrDefault()?.Co ??
dGCPayload.Recovery?.FirstOrDefault()?.Co;
if (result == null) {
throw new ArgumentException($"couldn't retrieve country from DGC. dgc : {JsonConvert.SerializeObject(dGCPayload)}");
}
return result;
}
private List < SignatureCertificate > GetFittingSignatures(DGCPayload dGCPayload, UzisData uzisData) {
try {
var country = GetCountryFromDGC(dGCPayload);
var result = uzisData.NationalCertificateSignatures.SignatureCertificate
.Where(x => x.Active)
.Where(x => x.Country == country)
.Where(x => x.CertificateType == "DSC")
.ToList();
return result;
} catch (Exception e) {
_logger.LogError(e, $"Filtering signatures from UZIS failed with exception");
return null;
}
}
private bool IsProperlySignedDGC(string certificateString, DGCPayload dGCPayload, Sign1Message coseMessage, UzisData uzisData) {
var fittingSignatures = GetFittingSignatures(dGCPayload, uzisData);
var result = false;
foreach(var signature in fittingSignatures) {
try {
var signatureVerificationResult = VerifyCoseSignature(coseMessage, signature.RawData);
_logger.LogInformation($"certificate {certificateString} signature validation against signature {signature} resulted in {signatureVerificationResult}");
result |= signatureVerificationResult;
} catch (Exception e) {
_logger.LogError(e, $"certificate {certificateString} signature validation against signature {signature} failed with exception");
}
}
return result;
}
public bool IsValidDgc(string certificateString, UzisData uzisData, out DGCPayload decodedDGCPayload, out VerificationResult verificationResult) {
decodedDGCPayload = null;
Sign1Message coseMessage = null;
try {
decodedDGCPayload = DecodeAndDeserialize(certificateString, out coseMessage);
if (coseMessage == null) {
_logger.LogInformation($"certificate {certificateString} decoded to null COSE");
verificationResult = VerificationResult.UnableToDecode;
return false;
}
} catch (Exception e) {
_logger.LogError(e, $"certificate {certificateString} decoding failed with exception");
verificationResult = VerificationResult.UnableToDecode;
return false;
}
var isProperlySigned = IsProperlySignedDGC(certificateString, decodedDGCPayload, coseMessage, uzisData);
if (!isProperlySigned) {
verificationResult = VerificationResult.InvalidSignature;
return false;
}
var isNotExpired = IsNotExpiredDGC(decodedDGCPayload, uzisData);
if (!isNotExpired) {
verificationResult = VerificationResult.Expired;
return false;
}
verificationResult = VerificationResult.Valid;
return true;
}
}
}
And the tricky base45
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DGCVerification
{
public class Base45Encoding
{
private static readonly Dictionary<byte, char> _encodingTable = new Dictionary<byte, char>
{
{0x0,'0'},
{0x1,'1'},
{0x2,'2'},
{0x3,'3'},
{0x4,'4'},
{0x5,'5'},
{0x6,'6'},
{0x7,'7'},
{0x8,'8'},
{0x9,'9'},
{0xA,'A'},
{0xB,'B'},
{0xC,'C'},
{0xD,'D'},
{0xE,'E'},
{0xF,'F'},
{0x10,'G'},
{0x11,'H'},
{0x12,'I'},
{0x13,'J'},
{0x14,'K'},
{0x15,'L'},
{0x16,'M'},
{0x17,'N'},
{0x18,'O'},
{0x19,'P'},
{0x1A,'Q'},
{0x1B,'R'},
{0x1C,'S'},
{0x1D,'T'},
{0x1E,'U'},
{0x1F,'V'},
{0x20,'W'},
{0x21,'X'},
{0x22,'Y'},
{0x23,'Z'},
{0x24,' '},
{0x25,'$'},
{0x26,'%'},
{0x27,'*'},
{0x28,'+'},
{0x29,'-'},
{0x2A,'.'},
{0x2B,'/'},
{0x2C,':'},
};
private static readonly Dictionary<char, byte> _decodingTable;
static Base45Encoding()
{
_decodingTable = _encodingTable.ToDictionary(x => x.Value, x => x.Key);
}
private static List<byte> ToBytesBase45(string charsBase45)
{
var result = new List<byte>(charsBase45.Length);
foreach (var character in charsBase45)
{
if (!_decodingTable.TryGetValue(character, out byte asByte))
{
throw new FormatException($"input string contains {character} with numeric value {char.GetNumericValue(character)} on index {charsBase45.IndexOf(character)} which is not valid base45 character");
}
result.Add(asByte);
}
return result;
}
private static List<ushort> ToShortsBase10(List<byte> bytesBase45)
{
var result = new List<ushort>(bytesBase45.Count);
ushort num = 0;
double pow = 0;
for (int i = 0; i != bytesBase45.Count; ++i, ++pow)
{
num += (ushort)(bytesBase45[i] * Math.Pow(45, pow));
if (pow == 2 || i == (bytesBase45.Count -1))
{
result.Add(num);
num = 0;
pow = -1;
}
}
return result;
}
private static List<byte> ToBytesBase256(List<ushort> shortsBase10, int charBase45Length)
{
var result = new List<byte>(shortsBase10.Count);
for (int i = 0; i != shortsBase10.Count; ++i)
{
var num = (byte)(shortsBase10[i] / 256);
if(!(i == (shortsBase10.Count - 1)
&& charBase45Length % 3 != 0
&& num == 0))
{
result.Add(num);
}
result.Add((byte)(shortsBase10[i] % 256));
}
return result;
}
public static byte[] Decode(string charsBase45)
{
if (charsBase45.Length % 3 == 1)
{
throw new FormatException("input string does not have correct length. mod 3 == 1. it isnt base45");
}
var bytesBase45 = ToBytesBase45(charsBase45);
var shortsBase10 = ToShortsBase10(bytesBase45);
var bytesBase256 = ToBytesBase256(shortsBase10, charsBase45.Length);
return bytesBase256.ToArray();
}
}
}
Decode/encode base45:
/// <summary>
/// https://tools.ietf.org/html/draft-faltstrom-baseBaseSize-01
/// TL/DR:
/// This encoding takes a byte array, splits it into 2 byte chunks and encodes each chunk as 3 characters.
/// Any remaining byte is encoded as 2 characters, padded with a '0' when the remaining byte has value < 45.
/// </summary>
public static class Base45Encoding
{
private const int BaseSize = 45;
private const int BaseSizeSquared = 2025;
private const int ChunkSize = 2;
private const int EncodedChunkSize = 3;
private const int SmallEncodedChunkSize = 2;
private const int ByteSize = 256;
private static readonly char[] _Encoding = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*',
'+', '-', '.', '/', ':' };
private static readonly Dictionary<char, byte> _Decoding = new(BaseSize);
static Base45Encoding()
{
for(byte i = 0; i < _Encoding.Length; ++i)
_Decoding.Add(_Encoding[i], i);
}
public static string Encode(byte[] buffer)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer));
var wholeChunkCount = buffer.Length / ChunkSize;
var result = new char[wholeChunkCount * EncodedChunkSize + (buffer.Length % ChunkSize == 1 ? SmallEncodedChunkSize : 0)];
if (result.Length == 0)
return string.Empty;
var resultIndex = 0;
var wholeChunkLength = wholeChunkCount * ChunkSize;
for (var i = 0; i < wholeChunkLength;)
{
var value = buffer[i++] * ByteSize + buffer[i++];
result[resultIndex++] = _Encoding[value % BaseSize];
result[resultIndex++] = _Encoding[value / BaseSize % BaseSize];
result[resultIndex++] = _Encoding[value / BaseSizeSquared % BaseSize];
}
if (buffer.Length % ChunkSize == 0)
return new string(result);
result[^2] = _Encoding[buffer[^1] % BaseSize];
result[^1] = buffer[^1] < BaseSize ? _Encoding[0] : _Encoding[buffer[^1] / BaseSize % BaseSize];
return new string(result);
}
public static byte[] Decode(string value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.Length == 0)
return Array.Empty<byte>();
var remainderSize = value.Length % EncodedChunkSize;
if (remainderSize == 1)
throw new FormatException("Incorrect length.");
var buffer = new byte[value.Length];
for (var i = 0; i < value.Length; ++i)
{
if (_Decoding.TryGetValue(value[i], out var decoded))
{
buffer[i] = decoded;
continue; //Earliest return on expected path.
}
throw new FormatException($"Invalid character at position {i}.");
}
var wholeChunkCount = buffer.Length / EncodedChunkSize;
var result = new byte[wholeChunkCount * ChunkSize + (remainderSize == ChunkSize ? 1 : 0)];
var resultIndex = 0;
var wholeChunkLength = wholeChunkCount * EncodedChunkSize;
for (var i = 0; i < wholeChunkLength; )
{
var val = buffer[i++] + BaseSize * buffer[i++] + BaseSizeSquared * buffer[i++];
result[resultIndex++] = (byte)(val / ByteSize); //result is always in the range 0-255 - % ByteSize omitted.
result[resultIndex++] = (byte)(val % ByteSize);
}
if (remainderSize == 0)
return result;
result[^1] = (byte)(buffer[^2] + BaseSize * buffer[^1]); //result is always in the range 0-255 - % ByteSize omitted.
return result;
}
}
This question is an evolution of this: Issue returning header byte from Python to Unity
Now the issue about header byte is solved, but I have a problem during the loop.
The data incoming good but after "n" times the loop break because receive a json string with an entire structure of data with open/close bracket PLUS the next structure of data with open bracket, some data and in the "image:" some data and many of the "\0" and unless the close bracket.. And the json parser obviously breaks. Why and what can I do? Thanks
Below the code revised:
IEnumerator Client()
{
while (!IsConnected(client))
{
try
{
client = new TcpClient(host, port);
s = client.GetStream();
byte[] byteBuffer = Encoding.UTF8.GetBytes("Connected to client");
s.Write(byteBuffer, 0, byteBuffer.Length);
while (true)
{
if (s.DataAvailable)
{
while (s.DataAvailable)
{
var header = new byte[4];
s.Read(header, 0, header.Length);
var fileSize = BitConverter.ToUInt32(header,0);
StringBuilder myCompleteMessage = new StringBuilder();
MemoryStream ms = new MemoryStream();
int increment = 0;
while (ms.Length < fileSize)
{
byte[] dataReceived = new byte[fileSize];
increment = s.Read(dataReceived, 0, dataReceived.Length);
ms.Write(dataReceived.Take(increment).ToArray(), 0, increment);
}
myCompleteMessage.AppendFormat("{0}", Encoding.UTF8.GetString(ms.GetBuffer()));
JSONNode data = JSONNode.Parse(myCompleteMessage.ToString());
....
// Do work with myCompleteMessage
yield return null;
}
}
}
}
}
}
I don't have your Unity environment here, so I can't have tested this.
Something like a loop (likely run in a coroutine or something that doesn't block Unity's default handling) like this should be enough to handle a stream of messages of the shape we established before (a 4-byte header signifying the message length, followed by that many bytes of JSON).
while (!IsConnected(client))
{
try
{
client = new TcpClient(host, port);
s = client.GetStream();
while (true)
{
var data = ReadPacket(s);
// Do work with data
}
}
}
ReadPacket should be something like
JSONNode ReadPacket(Stream s)
{
var buffer = new byte[131072];
// Read 4 bytes (we will assume we can always read that much)
var n = s.Read(buffer, 0, 4);
if(n < 4) throw new Exception("short read");
var fileSize = BitConverter.ToUInt32(buffer, 0);
Debug.Print("Reading a blob of length", fileSize);
// Memory stream to accumulate the reads into.
var ms = new MemoryStream();
while (ms.Length < fileSize)
{
// Figure out how much we can read -- if we're near the end,
// don't overread.
var maxRead = Math.Min(buffer.Length, fileSize - ms.Length);
var increment = s.Read(buffer, 0, maxRead);
// ... and write them into the stream.
ms.Write(buffer, 0, increment);
Debug.Print("Read", ms.Length, "of", fileSize);
}
// Decode the bytes to UTF-8 and parse.
return JSONNode.Parse(Encoding.UTF8.GetString(ms.GetBuffer()));
}
Now when I understand how the code works, I would like to translate it to C++.
The original Python code:
def recv_all_until(s, crlf):
data = ""
while data[-len(crlf):] != crlf:
data += s.recv(1)
return data
Here's what I tried:
std::string recv_all_until(int socket, std::string crlf)
{
std::string data = "";
char buffer[1];
memset(buffer, 0, 1);
while(data.substr(data.length()-2, data.length()) != crlf)
{
if ((recv(socket, buffer, 1, 0)) == 0)
{
if (errno != 0)
{
close(socket);
perror("recv");
exit(1);
}
}
data = data + std::string(buffer);
memset(buffer, 0, 1);
}
return data;
}
But it shows:
terminate called after throwing an instance of 'std::out_of_range'
what(): basic_string::substr
I understand that the problem is inside the while loop since at first the data string is empty. So how to improve this to make it work the same as it works in Python? Thank you.
You have the problem in the first iteration of your while loop:
Since the data is an empty string, data.length() is equal to 0, and thus you're calling data.substr(-2, 0).
To fix this, you need to add a check for the line length to the while statement.
Also, there's a method of finding such mistakes faster than writing a stackoverflow question about it. Consider reading this article.
If we first change your Python code a bit:
def recv_all_until(s, crlf):
data = ""
while not data.endswith(crlf):
data += s.recv(1)
return data
What we need to do in C++ becomes much clearer:
bool ends_with(const std::string& str, const std::string& suffix)
{
return str.size() >= suffix.size() &&
std::equal(suffix.rbegin(), suffix.rend(), str.rbegin());
}
std::string recv_all_until(int socket, const std::string& crlf)
{
std::string data = "";
char buffer[1];
memset(buffer, 0, 1);
while (!ends_with(data, crlf))
{
if ((recv(socket, buffer, 1, 0)) == 0)
{
if (errno != 0)
{
close(socket);
perror("recv");
exit(1);
}
}
data = data + std::string(buffer);
memset(buffer, 0, 1);
}
return data;
}
I'd like to be able to detect progressive jpegs using python and convert them to non progressive.
(I'm writing a tool to manage images for android and progressive jpegs seem to break it.)
I apologise in advance for providing a php based answer, whereas the question was asked about python. Nevertheless, I think it adds value and can be useful.
Before attempting to convert a progressive image to non-progressive, it is good to have a detection method for progressive Jpeg.
Here is the php function that does it, could easily be rewritten in other languages (python would be a candidate), as it reads binary data and Jpeg markers (and therefore does not rely on language specific library)
public function checkProgressiveJPEG($filepath) {
$result = false;
// $this->log = 'started analysis...';
// http://en.wikipedia.org/wiki/Jpeg
// for more details on JPEG structure
// SOI [0xFF, 0xD8] = Start Of Image
// SOF0 [0xFF, 0xC0] = Start Of Frame (Baseline DCT)
// SOF2 [0xFF, 0xC2] = Start Of Frame (Progressive DCT)
// SOS [0xFF, 0xDA] = Start Of Scan
if(file_exists($filepath)) {
$fs = #fopen($filepath, "rb");
$bytecount = 0;
$byte_last = 0;
$buffer = 0;
$buffer_length = 4*1024;
$begins_with_SOI = false;
while($buffer = fread($fs, $buffer_length)) {
// always carry over previous ending byte
// just in case the buffer is read after a 0xFF marker
if($byte_last) {
$buffer = $byte_last.$buffer;
}
$byte_last = 0;
preg_match("/\.$/", $buffer, $matches);
if(count($matches)) {
$byte_last = $matches[0];
}
// check if it begins with SOI marker
if(!$begins_with_SOI) {
preg_match("/^\\xff\\xd8/", $buffer, $matches);
if(count($matches)) {
$begins_with_SOI = true;
} else {
// $this->log = 'does not start with SOI marker';
$result = false;
break;
}
}
// check if SOS or SOF2 is reached
preg_match("/\\xff(\\xda|\\xc2)/", $buffer, $matches);
if(count($matches)) {
if(bin2hex($matches[0]) == 'ffda') {
// $this->log = 'SOS is reached and SOF2 has not been detected, so image is not progressive.';
$result = false;
break;
} else if(bin2hex($matches[0]) == 'ffc2') {
// $this->log = 'SOF2 is reached, so image is progressive.';
$result = true;
break;
}
}
} // end while
fclose($fs);
} // end if
return $result;
}