/// <summary> /// Converts the users image to the size they specify /// </summary> /// <param name="sender"></param> /// <param name="e"></param> public void WidthValue_ValueChanged(object sender, EventArgs e) { if (!loading) { converted = false; //if no image is present, don't try to resize if (UserImageBox.Image == null) { return; } //maxSize needs to be set to the new resized with value //if not, upon second conversion attempt it will still be the original width of 100 maxSize = Convert.ToInt32(Math.Round(WidthValue.Value, 0)); //if image is present, resize it to specified width. resized = ConvertImg.resizeImage(toConvert, toConvert.Width, toConvert.Height, maxSize); //store resized image, ready to be colour matched and converted to DMC olny colours numericUpDown1.Value = resized.Height; //redraw the image ResetProgressBar(); Invalidate(); } }
//Load everything from save file public void LoadSession() { ApplicationData loadData = new ApplicationData(); string json; try { if (loadLast) { json = File.ReadAllText(AppDomain.CurrentDomain.BaseDirectory + $"/LastSession.json"); } else { openFileDialog1.Filter = "Saved Conversions (*.json) | *.json"; openFileDialog1.InitialDirectory = AppDomain.CurrentDomain.BaseDirectory + "Saves"; openFileDialog1.ShowDialog(); json = File.ReadAllText(openFileDialog1.FileName); } loadData = JsonConvert.DeserializeObject <ApplicationData>(json); loading = true; converted = true; imageLoaded = true; sourceFile = loadData.sourceFile; fileName = loadData.fileName; dmcDataStore = loadData.dmcDataStrore; rgbArray = loadData.rgbArray; rgbArrayToDrawFrom = loadData.rgbArrayToDrawFrom; selectedDMCValues = loadData.selectedDMCValues; maxSize = loadData.maxSize; resized = ConvertImg.resizeImage(Image.FromFile(loadData.sourceFile), loadData.imgWidth, loadData.imgHeight, maxSize); toConvert = resized; UserImageBox.Image = resized; tickedCount = loadData.tickedCount; numericUpDown2.Value = loadData.threadAmount; threadAmount = loadData.threadAmount; numericUpDown3.Value = loadData.imageGridSize; numericUpDown1.Value = loadData.imgHeight; WidthValue.Value = loadData.imgWidth; imgWidth = loadData.imgWidth; imgHeight = loadData.imgHeight; markedPositions = loadData.markedPositions; loading = false; progressBar.Value = 100; ProgressBarText.Text = "Conversion Complete"; } catch (Exception) { return; } Invalidate(); }
public void LoadImageButon_Click(object sender, EventArgs e) { #region Load Image if (!loading) { //sets current progress bar to 0 progressBar.Value = 0; //only allows user to pick and load image files openFileDialog1.Filter = "Image files (*.jpg, *.jpeg, *.jpe, *.jfif, *.png) | *.jpg; *.jpeg; *.jpe; *.jfif; *.png"; //assigns the picked file a variable name 'imageToConvert' DialogResult imageToConvert = openFileDialog1.ShowDialog(); //get path of selected file, and change file to png sourceFile = openFileDialog1.FileName; string targetFile = Path.ChangeExtension(sourceFile, "png"); fileName = sourceFile.Split('\\').Last(); fileName = fileName.Substring(0, fileName.Length - 4); } //load image png and assign it to an Image variable //this is here to catch the event a user opens the load dialogue and presses the cancel button, without loading an image. try { image = Image.FromFile(sourceFile); imageLoaded = true; } catch (Exception) { return; } //load image to image display box, set it to fill the box if (image.Width > image.Height) { UserImageBox.Image = ConvertImg.resizeImage(image, image.Width, image.Height, UserImageBox.Width); } else { float ratio = (float)image.Width / (float)image.Height; int newWidth = (int)(ratio * UserImageBox.Height); UserImageBox.Image = ConvertImg.resizeImage(image, image.Width, image.Height, newWidth); } toConvert = image; //resize image to 100 stitches resized = ConvertImg.resizeImage(toConvert, toConvert.Width, toConvert.Height, maxSize); WidthValue.Value = 100; #endregion }
//run the conversion in an async task to free up the ui thread //prevents the program looking like it has frozen //also prevents "not responding" when trying to use the program while converting private async Task RunConversion() { //WARNING //if amount of selectable dmc threads changes in the future, this will not display the correct number present in the palette //currently starts at 454 as every checkbox is reset to false upon starting a new conversion. //so when count is incremented, it was starting from -454, not 0; int count = 454; //WARNING var progress = new Progress <int>(value => { base.Invoke((Action) delegate { progressBar.Value = ConvertImg.Clamp(0, 100, value); }); }); var unCheckItem = new Progress <int>(index => { base.Invoke((Action) delegate { dmcPaletteBox.SetItemChecked(index, false); count--; }); }); //should pass all the values to be checked at the end of the conversion, and set them in one call var checkItem = new Progress <int>(index => { base.Invoke((Action) delegate { dmcPaletteBox.SetItemChecked(index, true); count++; paletteCount.Text = "Palette Count\n" + count.ToString() + " / " + dmcPaletteBox.Items.Count.ToString(); }); }); var loadingText = new Progress <string>(str => { base.Invoke((Action) delegate { ProgressBarText.Text = str; }); }); await Task.Run(() => { //call the process image method the convert our image to DMC values and display the values on a grid //store the returned dmc pixel array and rgbArray to recall them if user accidentally marks the wrong grid cell Console.WriteLine($"drawing with height and width of x ={resized.Width}, y={resized.Height}"); Tuple <string[, ], Color[, ]> tupleReturn = ConvertImg.processImage(loadingText, progress, unCheckItem, checkItem, threadAmount, resized, selectedDMCValues, progressBar, ProgressBarText, algo, allDMCValues, dmcPaletteBox, dither, ditherFactor, commonColourSensitivity.Value); dmcDataStore = tupleReturn.Item1; rgbArray = tupleReturn.Item2; rgbArrayToDrawFrom = tupleReturn.Item2; Console.WriteLine($"array from convert image func x ={rgbArrayToDrawFrom.GetLength(1)}, y={rgbArrayToDrawFrom.GetLength(0)}"); //update palette counter, just in case user generated threads and didnt pick any tickedCount = dmcPaletteBox.CheckedItems.Count; //tell program that a conversion has just taken place //this is here to prevent drawing the grid colors, before the grid colours have been established //DrawImage function checks for this converted = true; //store selected values, this will be used if creating a pdf selectedDMCValues = new List <String>(dmcPaletteBox.CheckedItems.Cast <String>()); Invalidate(); }); }
public void Create(Image image, string[,] dmcGrid, List <string> selectedDMCValues, string fileName) { Dictionary <string, string> dmcToShortText = new Dictionary <string, string>(); Dictionary <string, Color> dmcValues = new Dictionary <string, Color>(); PdfDocument document = new PdfDocument(); //Create multiple pages to fit an entire conversion prjoect on //first need to break up the image, using the DMCgridarray's width and height. //then calculate how many pages it needs by breaking it up into chunks of a set width and height //create a page for each chunk using a set of starting co-ordinates that reference the DMCgrid //starting by assigning each dmc value an id //assemble the dictionary of dmc values and assign them a short text code #region Assign DMC values a short text code //sets how wide the grid is on the page int gridsize = 18; //stores what letters are accessed when creating the dict of dmc codes int xyzID = 0; int numID = 0; //string which is indexed for the creation of short dmc codes string alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; //itterate over each grid, if the dmc value is not present in the dict, add it as a key //and make its value a letter from the alphabet and a number. starting at A0. //increment the latter, so the next time a new value is stored it is the next letter of alphabet //if alphabet gets to Z, increase the number by 1 and start from A again for (int i = 0; i < dmcGrid.GetLength(0); i++) { for (int j = 0; j < dmcGrid.GetLength(1); j++) { if (!dmcToShortText.ContainsKey(dmcGrid[i, j])) { dmcToShortText.Add(dmcGrid[i, j], $"{alphabet[xyzID]}{numID}"); if (alphabet[xyzID] == 'Z') { xyzID = 0; numID++; } else { xyzID++; } } } } #endregion #region This is where the image is broken into chunks //store the width and height of the grid int gridCellNumW = dmcGrid.GetLength(0); int gridCellNumH = dmcGrid.GetLength(1); //Set how many grid cells you want on each page(width and height) int cellNumWPerPage = 20; int cellNumHPerPage = 20; //create a list of each top left coordinate in each chunk List <int[]> chunkTopLeft = new List <int[]>(); for (int i = 0; i < gridCellNumW / cellNumWPerPage; i++) { for (int j = 0; j < gridCellNumH / cellNumHPerPage; j++) { chunkTopLeft.Add(new int[] { i *cellNumWPerPage, j *cellNumHPerPage }); } } #endregion #region Create dmcValue dictionaryto hold dmc id and colour data foreach (var item in selectedDMCValues) { string[] dmcString = item.Split('\t'); Color col = Color.FromArgb(Convert.ToInt32(dmcString[2]), Convert.ToInt32(dmcString[3]), Convert.ToInt32(dmcString[4])); dmcValues.Add(dmcString[0], col); } #endregion #region This is where the drawing happens //This needs to be done for each page //Starting with the first, as it is different every time. //After page one, the page drawing can be itterated through, as they all follow the same procedure #region Page One //Create intro page, including image of conversion and a map showing the area each page covers //Add a new page (page1) document.AddPage(); //first page graphics, and brushes/fonts XGraphics gfx = XGraphics.FromPdfPage(document.Pages[0]); XPdfFontOptions options = new XPdfFontOptions(PdfFontEncoding.Unicode); //create image var (img) from users image, that will be shown on the first page of the pdf MemoryStream strm = new MemoryStream(); //resize image //if image is taller than is wide, resize it based on its height if (image.Width > image.Height) { image = ConvertImg.resizeImage(image, image.Width, image.Height, 765); } else { image = ConvertImg.resizeImage(image, image.Width, image.Height, 765 / 2); } image.Save(strm, System.Drawing.Imaging.ImageFormat.Png); XImage img = XImage.FromStream(strm); //create fonts for text, and brushes for the colours of text and grid XFont font = new XFont("Consolas", 10, XFontStyle.Regular, options); XBrush brush = XBrushes.Black; XPen thinLine = new XPen(XColor.FromArgb(Color.Black.ToArgb()), 1); XPen wideLine = new XPen(XColor.FromArgb(Color.Black.ToArgb()), 3); //draw the image (since gfx is referencing page 1, this is where it is drawn) int imgOffX = 10; int imgOffY = 10; gfx.DrawImage(img, imgOffX, imgOffY); //draw the text codes for each dmc value that is used in the project //convert dict of dmc text codes to a string for drawing int startX = imgOffX; int lineSpace = 10; int startY = imgOffY + img.PixelHeight; int count = 1; foreach (var item in dmcToShortText.Keys) { string keyString = $"{dmcToShortText[item]} = {item}"; gfx.DrawString(keyString, font, brush, startX, (count * lineSpace) + startY - 50); //draw a square next to each string showing its thread colour (needs converted colour data) XPen dmcCol = new XPen(XColor.FromArgb(dmcValues[item].ToArgb()), 6); gfx.DrawRectangle(dmcCol, new XRect(new XPoint(startX + 60, (count * lineSpace) + startY - 56), new XSize(5, 5))); count++; } #endregion End of page one region #region Pages with chart //This is where the grid will be broken down to make it readable across multiple pages. //These 2 variables determine how many cells there are (width and height) //currently it is the maximum, but i should be able to set it to say 30 and 30, //and have the page only display 30 across and 30 down int h = 30; int w = 30; //first, break down the grid into h by w chunks. //store chunk start positions in a list List <int[]> chunks = new List <int[]>(); //calculate how many chunks wide and high int chunkH = (int)Math.Ceiling(dmcGrid.GetLength(0) / (float)h); int chunkW = (int)Math.Ceiling(dmcGrid.GetLength(1) / (float)w); for (int i = 0; i < chunkH; i++) { for (int j = 0; j < chunkW; j++) { chunks.Add(new int[] { j *w, i *h }); Console.WriteLine($"new chunk at {j * w},{i * h}"); } } //create a page for every chunk for (int a = 0; a < chunks.Count; a++) { document.AddPage(); gfx = XGraphics.FromPdfPage(document.Pages[a + 1]); //a+1 because first page already exists int textwOffset = 20; int texthOffset = 20; int linewOffset = textwOffset + -4; int linehOffset = texthOffset + -12; //drawtext in a grid that shows short text code of each dmc pixel //start at chunk location and go up to chunk location +h,w int y = 0; int x = 0; for (int i = chunks[a][1]; i < chunks[a][1] + h; i++) { for (int j = chunks[a][0]; j < chunks[a][0] + w; j++) { if (i < dmcGrid.GetLength(0) && j < dmcGrid.GetLength(1)) { gfx.DrawString(dmcToShortText[dmcGrid[i, j]], font, brush, (x * gridsize) + textwOffset, (y * gridsize) + texthOffset); } x++; } y++; x = 0; } //draw a line grid over the text //horizontal lines, with 10th line being thicker y = 0; x = 0; for (int i = chunks[a][1]; i < chunks[a][1] + h + 1; i++) { if (i % 10 == 0) { if (i <= dmcGrid.GetLength(0)) { gfx.DrawLine(wideLine, new XPoint(0 + linewOffset, (y * gridsize) + linehOffset), new XPoint((w * gridsize) + linewOffset, (y * gridsize) + linehOffset)); } } else { if (i <= dmcGrid.GetLength(0)) { gfx.DrawLine(thinLine, new XPoint(0 + linewOffset, (y * gridsize) + linehOffset), new XPoint((w * gridsize) + linewOffset, (y * gridsize) + linehOffset)); } } y++; } //vertical lines, with 10th line being thicker for (int i = chunks[a][0]; i < chunks[a][0] + w + 1; i++) { if (i % 10 == 0) { if (i <= dmcGrid.GetLength(1)) { gfx.DrawLine(wideLine, new XPoint((x * gridsize) + linewOffset, linehOffset), new XPoint((x * gridsize) + linewOffset, (h * gridsize) + linehOffset)); } } else { if (i <= dmcGrid.GetLength(1)) { gfx.DrawLine(thinLine, new XPoint((x * gridsize) + linewOffset, linehOffset), new XPoint((x * gridsize) + linewOffset, (h * gridsize) + linehOffset)); } } x++; } } #endregion End of chart region #endregion End of drawing region string pdf = AppDomain.CurrentDomain.BaseDirectory + $"/PDF_Charts/{fileName}.pdf"; //save the pdf document.Save(pdf); //opens the pdf for the user Process.Start(pdf); }