A little bug in fpsimages.pas lazarus / 49-FPSpreadsheet ---------------------------------------------------------------------- Roni Wolf · 2025-05-07 20:41 Hi! First post here! (sorry if my english is not good, my primary language is portuguese). I'm working on a project that uses FPSpreadsheet to read a spreadsheet with several embedded figures. I encountered an issue where some JPEG images were not being recognized correctly, leading to "Image format not supported" warnings. After debugging the fpsimages.pas unit, specifically the image type identification and metadata reading functions, I identified two main areas for improvement: Handling of different APPn segments in JPEG files: The original code had a strict size check after reading the first APP0 segment (FF E0). If subsequent APPn segments (FF Ex) had a different size than expected for a standard APP0, the process of reading image dimensions (width and height) would terminate prematurely. This prevented the code from reaching the Start Of Frame (SOF) marker where the dimensions are typically stored. Proposed Solution: I modified the size check for APP0 segments to be less restrictive, ensuring that the code continues to scan for other segments, including the SOF marker (FF C0 to FF C3), even if other APPn segments with different sizes are encountered. This allows for correct extraction of dimensions from a wider range of JPEG files. Resetting DPI in GetTIFSize function: The GetTIFSize function, used to read metadata from TIFF images (and also called within the JPEG parsing logic for EXIF blocks), was initializing the DPI (dots per inch) values (dpiX, dpiY) to zero. If the EXIF block in a JPEG file did not contain DPI information, or if there was an error reading it, the DPI would be incorrectly reported as zero, even if DPI information was present in the APP0 segment. Proposed Solution: I modified GetTIFSize to store the initial DPI values passed to it and only reset them to a default "invalid" value (-1 in my implementation) internally. If the function fails to read DPI from the TIFF/EXIF data, it restores the original DPI values passed to it. This ensures that if DPI is available in the APP0 segment of a JPEG, it is not overwritten by a failed DPI read from the EXIF block. Outcome: With these changes, FPSpreadsheet is now able to correctly identify and extract dimensions and DPI from a broader range of JPEG images embedded in XLSX files that were previously causing "Image format not supported" errors. I believe these improvements would enhance the robustness of the image handling capabilities in FPSpreadsheet. I would like to suggest incorporating these changes into the library. I can provide the modified code snippets above: In GetJPGSize: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   while (AStream.Position < AStream.Size) and (rec.Marker = $FF) do begin    if AStream.Read(rec, SizeOf(rec)) < SizeOf(rec) then exit;    rec.RecSize := BEToN(rec.RecSize);    p := AStream.Position - 2;    case rec.RecType of      $E0:  // APP0 record        if (rec.RecSize >= SizeOf(TAPP0Record) - 2) then // -2 because RecSize includes the 2 bytes of the size itself        begin          AStream.Read(app0{%H-}, SizeOf(app0));          if stricomp(pchar(app0.JFIF), 'JFIF') = 0 then          begin            dpiX := BEToN(app0.XDensity);          dpiY := BEToN(app0.YDensity);            u := app0.Units;          end;        end;      $E1:   // APP1 record (EXIF) ...   In GetTIFSize: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i var  header: TTifHeader = (BOM:0; Sig:0; IFD:0);  dirEntries: Word;  field: TIFD_Field = (Tag:0; FieldType:0; ValCount:0; ValOffset:0);  i: Integer;  bo: TByteOrder;  num, denom: LongInt;  units: Word;  p, pStart: Int64;  initialDPIX: Double; // Local variable to store the initial value of dpiX  initialDPIY: Double; // Local variable to store the initial value of dpiY begin  Result := false;  AWidth := 0;  AHeight := 0;  //dpiX := 0;  //dpiY := 0;  units := 0;   initialDPIX := dpiX; // Saves passed DPI values  initialDPIY := dpiY;   dpiX := -1; // Set to "invalid" for internal tracking  dpiY := -1;   // Remember current stream position because procedure is called also from  // jpeg Exif block. ...   case units of    1: begin dpiX := 96; dpiY := 96; end;    2: ;  // is already inches, nothing to do    3: begin dpiX := dpiX*2.54; dpiY := dpiY * 2.54; end;  end;   if (dpiX = -1) then dpiX := initialDPIX; // Restore if reading failed  if (dpiY = -1) then dpiY := initialDPIY;   Result := true;  I hope these small corrections are useful! And first of all, thank you very much for this beautiful library! Greetings from Brazil, Roni. ---------------------------------------------------------------------- wp · 2025-05-07 21:00 Thank you. Could you please add images for which the current procedure fails so that I can verify the issue? ---------------------------------------------------------------------- Roni Wolf · 2025-05-07 21:12 Quote from: wp on May 07, 2025, 11:00:04 pmThank you. Could you please add images for which the current procedure fails so that I can verify the issue? Sure! Attached is one of the images present in the XLSX spreadsheet that causes this problem... Thanks! Roni. ---------------------------------------------------------------------- wp · 2025-05-08 12:01 It seems to me that the first issue is that this is some kind of "malformed" jpeg image because it contains two JFIF segments ($FF $E0), not sure whether this is allowed or not, but I've never seen this before. But anyway, other software can handle this file, therefore, FPSpreadsheet should be able to, as well. The other issue is that the function GetJpgSize of fpspreadsheet can exit before all segments have been scanned in which the required information can be found. And the TIFF metadata routine may destroy values already collected. I uploaded an improved version to svn, at least it detects your test file correctly, and spready is able to display an xlsx file in which that jpg file is embedded. Since there is some rework of the chart part of fpspreadsheet at the moment the current version will not yet be made available in OPM. If you don't use the svn version you probably can just use the file fpsimage.pas in your fpspreadsheet folder (not 100% sure if this works, therefore make a backup copy of this file before you change anything). ---------------------------------------------------------------------- Roni Wolf · 2025-05-09 17:49 Hello wp! I had never seen an image like this before, and the spreadsheet I received from a client has over 100 images with this problem (there is still one unrecognized image, but I haven't identified which one to debug yet). Even so, thanks for putting it in svn, for now I changed it directly in fpsimages.pas. It doesn't fix it in development time, but it does fix it in runtime!  Another thing I noticed is that the figures don't resize along with the changes in ZoomFactor. In which file is this handled? I can take a look to see if I can do something about it too. Thanks for the quick response! Cheers, Roni ---------------------------------------------------------------------- wp · 2025-05-09 19:16 Quote from: Roni Wolf on May 09, 2025, 07:49:31 pmIt doesn't fix it in development time, but it does fix it in runtime! Sorry, I don't understand what you mean Quote from: Roni Wolf on May 09, 2025, 07:49:31 pmAnother thing I noticed is that the figures don't resize along with the changes in ZoomFactor. Which figures? The width and height of the image? IIRC, this should be considered before scaling. Similar to column width or row height. The zoom factor should be applied at the end and should not modify any dimensions stored in the worksheet/workbook. ---------------------------------------------------------------------- Roni Wolf · 2025-05-09 21:26 Quote from: wp on May 09, 2025, 09:16:52 pmQuote from: Roni Wolf on May 09, 2025, 07:49:31 pmIt doesn't fix it in development time, but it does fix it in runtime! Sorry, I don't understand what you mean When I connect to this spreadsheet inside Lazarus (design time) through TsWorkbookSource, I get several error messages (one for each image), but when I run the compiled application (runtime), I don't get any error messages when opening the spreadsheet. This is not a problem for me now, the most important thing is not to have errors during the execution of the application. Don't worry about that. Quote from: Roni Wolf on May 09, 2025, 07:49:31 pmAnother thing I noticed is that the figures don't resize along with the changes in ZoomFactor. Which figures? The width and height of the image? IIRC, this should be considered before scaling. Similar to column width or row height. The zoom factor should be applied at the end and should not modify any dimensions stored in the worksheet/workbook. Yes, the height and width of the image... When a ZoomFactor is applied (say, 50%), in Excel the images also decrease by 50% (or close to that). In FPSpreadsheet the figures maintain the same size, even if the ZoomFactor is increased or decreased a lot (say 5%). It is only in the figure presentation routine, I don't want to change anything in the figure properties. Since I don't know the FPSpreadsheet file tree, I don't know in which file I should look for the routine that shows the images on the TsWorksheetGrid canvas... ---------------------------------------------------------------------- wp · 2025-05-09 22:15 Ah, I see, you did not mention TsWorksheetGrid. Yes, I think the scale factor was ignored when image support was added later. The new svn version fixes this. If you do not use svn you can patch it yourself: All the changes are in the method TsCustomWorksheetGrid.DrawImages in unit source/visual/fpsworksheetgrid.pas. Essentially, multiply img^.ScaleX, img^.ScaleY, img^.OffsetX and img^.OffsetY by Workbook.ZoomFactor - that's it. ---------------------------------------------------------------------- Roni Wolf · 2025-05-14 20:14 Hello wp! I apologize for the delay in responding, I was looking for another small bug... And sorry for not mentioning TsWorksheetGrid... It's not because it seemed obvious to me, that it really was obvious to everyone... Duh....  I noticed that the IMG object was being written to memory with the RowOffset and ColOffset inverted... This happens in the XLSXOOXML unit, inside the TsSpreadOOXMLReader.ReadEmbeddedObjs procedure. At the end of the procedure, the WriteImage function of the sheet object (TsWorkSheet) is called. See the call: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i sheet.WriteImage(data.FromRow, data.FromCol,data.ImgIndex,data.FromRowOffs, data.FromColOffs,scaleX, scaleY);  The function declaration (in the include fpspreadsheet_embobj.inc) is this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function TsWorksheet.WriteImage(ARow, ACol: Cardinal; AImageIndex: Integer;AOffsetX: Double = 0.0; AOffsetY: Double = 0.0;AScaleX: Double = 1.0; AScaleY: Double = 1.0): Integer;  Note that "data.FromRowOffs" is being passed to AOffsetX (X should be columns) and "data.data.FromColOffs" to AOffsetY (Y should be rows). Just invert this call and the objects started to appear in the correct positions. I don't know if this change has any impact on other points of the code, but in my test spreadsheet everything started to be in the correct place. Fixed function call: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i sheet.WriteImage(data.FromRow, data.FromCol,data.ImgIndex,data.FromColOffs, data.FromRowOffs,scaleX, scaleY);  Hope this helps! I'm still trying to figure out why several images aren't being displayed in the Grid. If I find out, I'll let you know here too! Thanks for this wonderful library! Roni ---------------------------------------------------------------------- Roni Wolf · 2025-05-15 00:32 Update: Some images are not being loaded because they are in oneCellAnchor nodes. I tried to modify the TsSpreadOOXMLReader.ReadDrawing procedure by setting a condition to handle oneCellAnchor but node.nodeName does not have the sibling of "xdr:pic", only "xdr:from", "xdr:ext" and "xdr:clientData". In "drawing1.xml" there is the sibling "xdr:pic". I still haven't found the reason because it is not in the node chain. Below is part of one of the figures in drawing1.xml: Code: XML  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i                         1                304800              4                54430                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                I will continue debugging, and if I find anything, I will post it here. Thanks! Roni ---------------------------------------------------------------------- wp · 2025-05-15 21:21 Quote from: Roni Wolf on May 14, 2025, 10:14:19 pmJust invert this call [...] Thanks for this fix. But it's only half of the story. The same exchanged x and y coordinates were found also in TsWorksheet.CalcImageExtent which was responsible for the bug in writing: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TsWorksheet.CalcImageExtent(AIndex: Integer; UsePixels: Boolean;  out ARow1, ACol1, ARow2, ACol2: Cardinal;  out ARowOffs1, AColOffs1, ARowOffs2, AColOffs2: Double;  out x,y, AWidth, AHeight: Double);var  img: TsImage;  obj: TsEmbeddedObj;begin  img := GetImage(AIndex);  ARow1 := img.Row;  ACol1 := img.Col;  ARowOffs1 := img.OffsetX;          // in workbook units  --- was: AColOffs1 := img.OffsetX  AColOffs1 := img.OffsetY;            // in workbook units  --- was: ARowOffs1 := img.OffsetY;   obj := FWorkbook.GetEmbeddedObj(img.Index);[...] Quote from: Roni Wolf on May 15, 2025, 02:32:44 amSome images are not being loaded because they are in oneCellAnchor nodes. Unfortunately embedded images are handled by Excel in a very confusing way. Thank you for debugging this. I usually approach this issue by writing small Excel files where only a single property is changed in a well-known way. Then I study the content of the xml files embedded inside the xlsx file structure. To simplify the many steps of unzipping, opening the xmls, navigating to the interesting nodes etc I wrote a simple utility "ZippedFileViewer" (https://github.com/wp- xyz/ZippedFileViewer) ---------------------------------------------------------------------- Roni Wolf · 2025-05-16 21:35 Quote from: wp on May 15, 2025, 11:21:50 pmQuote from: Roni Wolf on May 14, 2025, 10:14:19 pmJust invert this call [...] Thanks for this fix. But it's only half of the story. The same exchanged x and y coordinates were found also in TsWorksheet.CalcImageExtent which was responsible for the bug in writing: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TsWorksheet.CalcImageExtent(AIndex: Integer; UsePixels: Boolean;  out ARow1, ACol1, ARow2, ACol2: Cardinal;  out ARowOffs1, AColOffs1, ARowOffs2, AColOffs2: Double;  out x,y, AWidth, AHeight: Double);var  img: TsImage;  obj: TsEmbeddedObj;begin  img := GetImage(AIndex);  ARow1 := img.Row;  ACol1 := img.Col;  ARowOffs1 := img.OffsetX;          // in workbook units  --- was: AColOffs1 := img.OffsetX  AColOffs1 := img.OffsetY;            // in workbook units  --- was: ARowOffs1 := img.OffsetY;   obj := FWorkbook.GetEmbeddedObj(img.Index);[...] Quote from: Roni Wolf on May 15, 2025, 02:32:44 amSome images are not being loaded because they are in oneCellAnchor nodes. Unfortunately embedded images are handled by Excel in a very confusing way. Thank you for debugging this. I usually approach this issue by writing small Excel files where only a single property is changed in a well-known way. Then I study the content of the xml files embedded inside the xlsx file structure. To simplify the many steps of unzipping, opening the xmls, navigating to the interesting nodes etc I wrote a simple utility "ZippedFileViewer" (https://github.com/wp- xyz/ZippedFileViewer) Hello wp! It really is quite confusing and the documentation is very sparse. I only started to understand something after reading a lot about DrawingML. That is, if I really managed to understand anything... I'm going to change the CalcImageExtent procedure, because I'll need to save the spreadsheet as well. Thank you very much! I've already downloaded your "ZippedFileViewer" project, but I still need to compile it. It will definitely be very useful. Thank you! I managed to add the processing of the "oneCellAnchor" nodes, I did some tests and everything seems to be working correctly. In my case, I still need to make some changes, because some figures in the XLSX spreadsheet are rotated. I'm already capturing the rotation in the routine, but the TEmbeddedObjData object doesn't have this property. Furthermore, in the FPSpreadsheetGrid unit it would be necessary to change the "TsCustomWorksheetGrid.DrawImages" procedure to handle this rotation. I won't have time to implement this now, but at least I'm already capturing the rotation angle of the image. Below is the procedure "TsSpreadOOXMLReader.ReadDrawing" with my modifications. If you find them useful, feel free to incorporate them into the library. Question: How can I edit a post in this forum? I couldn't find where to do that... Roni Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TsSpreadOOXMLReader.ReadDrawing(ANode: TDOMNode;   AWorksheet: TsBasicWorksheet);var  node, child, child2, child3: TDOMNode;  nodeName: String = '';  rID, fileName, stmp: String;  fromCol, fromRow, toCol, toRow: Integer;  fromColOffs, fromRowOffs, toColOffs, toRowOffs: Double;  data: TEmbeddedObjData;  sheetData: TSheetData;  RotateAngle: Double;begin  if ANode = nil then    exit;   sheetData := TSheetData(FSheetList[TsWorksheet(AWorksheet).Index]);  ANode := ANode.FirstChild;   while Assigned(ANode) do  begin    nodeName := ANode.NodeName;    fromCol := -1;     fromColOffs := 0.0;    fromRow := -1;     fromRowOffs := 0.0;    toCol := -1;       toColOffs := 0.0;    toRow := -1;       toRowOffs := 0.0;    rID := '';         fileName := '';    if nodeName = 'xdr:twoCellAnchor' then    begin    node := ANode.FirstChild;      while Assigned(node) do begin         nodeName := node.NodeName;        if nodeName = 'xdr:from' then      begin          child := node.FirstChild;          while Assigned(child) do begin            nodeName := child.NodeName;          if nodeName = 'xdr:col' then              fromCol := StrToIntDef(GetNodeValue(child), -1)            else if nodeName = 'xdr:row' then              fromRow := StrToIntDef(GetNodeValue(child), -1)            else if nodeName = 'xdr:colOff' then              fromColOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0))            else if nodeName = 'xdr:rowOff' then              fromRowOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0));            child := child.NextSibling;          end;        end else         if nodeName = 'xdr:to' then        begin          child := node.FirstChild;        while Assigned(child) do begin            nodeName := child.NodeName;            if nodeName = 'xdr:col' then              toCol := StrToIntDef(GetNodeValue(child), -1)            else if nodeName = 'xdr:row' then              toRow := StrToIntDef(GetNodeValue(child), -1)            else if nodeName = 'xdr:colOff' then              toColOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0))          else if nodeName = 'xdr:rowOff' then              toRowOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0));            child := child.NextSibling;          end;        end else        if nodeName = 'xdr:pic' then        begin          child := node.FirstChild;        while Assigned(child) do begin            nodeName := child.NodeName;            if nodeName = 'xdr:blipFill' then          begin              child2 := child.FirstChild;              while Assigned(child2) do begin                nodeName := child2.NodeName;                if nodeName = 'a:blip' then                  rID := GetAttrValue(child2, 'r:embed');                child2 := child2.NextSibling;              end;            end else            if nodeName = 'xdr:nvPicPr' then begin              child2 := child.FirstChild;              while Assigned(child2) do begin              nodeName := child2.NodeName;                if nodeName = 'xdr:cNvPr' then                  fileName := GetAttrValue(child2, 'descr');                child2 := child2.NextSibling;              end;            end;            child := child.NextSibling;          end;        end;        node := node.NextSibling;      end;    end;   // oneCellAnchor    if nodeName = 'xdr:oneCellAnchor' then    begin    node := ANode.FirstChild;      while Assigned(node) do begin      nodeName := node.NodeName;        if nodeName = 'xdr:from' then      begin          child := node.FirstChild;          while Assigned(child) do begin            nodeName := child.NodeName;          if nodeName = 'xdr:col' then              fromCol := StrToIntDef(GetNodeValue(child), -1)            else if nodeName = 'xdr:row' then              fromRow := StrToIntDef(GetNodeValue(child), -1)            else if nodeName = 'xdr:colOff' then              fromColOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0))            else if nodeName = 'xdr:rowOff' then              fromRowOffs := EMUToMM(StrToInt64Def(GetNodeValue(child), 0));            child := child.NextSibling;          end;        end else        if nodeName = 'xdr:pic' then        begin          child := node.FirstChild;        while Assigned(child) do begin            nodeName := child.NodeName;            if nodeName = 'xdr:blipFill' then          begin              child2 := child.FirstChild;              while Assigned(child2) do begin                nodeName := child2.NodeName;                if nodeName = 'a:blip' then                  rID := GetAttrValue(child2, 'r:embed');                child2 := child2.NextSibling;              end;            end else            if nodeName = 'xdr:nvPicPr' then begin              child2 := child.FirstChild;              while Assigned(child2) do begin              nodeName := child2.NodeName;                if nodeName = 'xdr:cNvPr' then                  fileName := GetAttrValue(child2, 'descr');                child2 := child2.NextSibling;              end;            end;              if nodeName = 'xdr:spPr' then begin              child2 := child.FirstChild;              while Assigned(child2) do begin                nodeName := child2.NodeName;                if nodeName = 'a:xfrm' then begin                  RotateAngle := StrToFloatDef(GetAttrValue(child2, 'rot'),0) / 60000; // Rotation of the image. 60000 is 1 degree                  child3 := child2.FirstChild;                  while Assigned(child3) do begin                  nodeName:=child3.NodeName;                    if nodeName = 'a:ext' then begin                      toColOffs := EMUToMM(StrToInt64Def(GetAttrValue(child3, 'cx'),0)) + fromColOffs;  // cx is the width of the image in EMU's                      toRowOffs := EMUToMM(StrToInt64Def(GetAttrValue(child3, 'cy'),0)) + fromRowOffs;  // cy is the height of the image in EMU's                  end;                    child3 := child3.NextSibling;                end;                end;                child2 := child2.NextSibling;              end;            end;            child := child.NextSibling;          end;        end;        node := node.NextSibling;      end;      toCol:=fromCol;      toRow:=fromRow;    end;     if (fromCol <> -1) and (toCol <> -1) and (fromRow <> -1) and (toRow <> -1) and (rID <> '') then    begin      data := TEmbeddedObjData.Create;      data.FromCol := fromCol;      data.FromColOffs := fromColOffs;      data.ToCol := toCol;      data.ToColOffs := toColOffs;      data.FromRow := fromRow;      data.FromRowOffs := fromRowOffs;      data.ToRow := toRow;      data.ToRowOffs := toRowOffs;      data.RelId := rId;      data.FileName := fileName;      data.MediaName := MakeXLPath(sheetData.DrawingRels.FindTarget(rID));      data.ImgIndex := -1;      data.Worksheet := AWorksheet;      data.IsHeaderFooter := false;      FEmbeddedObjList.Add(data);    end;          ANode := ANode.NextSibling;  end;end; ---------------------------------------------------------------------- wp · 2025-05-16 22:31 Can you send me an xlsx file with an embedded image in one-cell-anchor mode (ideally only a single image in the file). Whatever I do in the GUI - I always end up with two-cell-anchor nodes in the xml. Quote from: Roni Wolf on May 16, 2025, 11:35:51 pmQuestion: How can I edit a post in this forum? I couldn't find where to do that... At the top of your posts, right-hand side, there is a "Modify" button. Of course, it works only for your own posts. ---------------------------------------------------------------------- Roni Wolf · 2025-05-18 19:38 Hello wp! Quote from: wp on May 17, 2025, 12:31:24 amCan you send me an xlsx file with an embedded image in one-cell-anchor mode (ideally only a single image in the file). Whatever I do in the GUI - I always end up with two-cell-anchor nodes in the xml. Yes, sure! In the "oneCellAnchor test 1.XLSX" spreadsheet, I left 2 images, one in oneCellAnchor and the other in twoCellAnchor. I believe that the difference is in the size of the image in relation to the size of the cell (height and width). When the original size of the image is smaller than the size of the cell, Excel should save it in the oneCellAnchor node, and when the image is larger, Excel saves it in the twoCellAnchor node. At least that's what I deduced. I believe that other changes will be necessary, because if the library was not prepared to read images within oneCellAnchor node, it probably will not be saving images within a oneCellAnchor either. Another task for my "ToDo" list.  If you need anything else, just ask me. Quote from: wp on May 17, 2025, 12:31:24 amQuote from: Roni Wolf on May 16, 2025, 11:35:51 pmQuestion: How can I edit a post in this forum? I couldn't find where to do that... At the top of your posts, right-hand side, there is a "Modify" button. Of course, it works only for your own posts. I don't know why, but this option doesn't appear for me, I believe it's because I'm new to the forum. Thanks for your help. Roni ---------------------------------------------------------------------- Roni Wolf · 2025-05-21 21:51 Hi wp! One question: When I load a spreadsheet into FpSpreadsheetGrid, it correctly adjusts the grid's ZoomFactor to what is recorded in the file, but when I save the modified spreadsheet, Excel reads the file with the "ZoomFactor" at 100%. Does the current code not record the ZoomFactor or am I not knowing the correct way to do this? Tanks! Roni ---------------------------------------------------------------------- wp · 2025-05-22 08:57 There is a workbook option boWriteZoomFactor which must be included if you want to write the zoom factor to file. To be honest, I cannot remember why I gave this property such a special treatment... Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program zoom_demo;uses  fpspreadsheet, fpsTypes, xlsxOOXML, fpsOpenDocument, xlsbiff8;var  book: TsWorkbook;  sheet: TsWorksheet;begin  book := TsWorkbook.Create;  try    sheet := book.AddWorksheet('Sheet');    sheet.WriteNumber(0, 0, 1.0);    sheet.WriteNumber(0, 1, 2.0);    sheet.WriteNumber(1, 0, 5.12);    sheet.ZoomFactor := 2.0;    book.Options := book.Options + [boWriteZoomFactor];    book.WriteToFile('zoom.xlsx', true);    book.WriteToFile('zoom.ods', true);    book.WriteToFile('zoom.xls', true);  finally    book.Free;  end;end. ---------------------------------------------------------------------- wp · 2025-05-22 14:19 Implemented reading of one-cell-anchor images, as well as image rotation angles from xlsx files, based on your patch. ---------------------------------------------------------------------- Roni Wolf · 2025-05-23 18:10 Quote from: wp on May 22, 2025, 10:57:49 amThere is a workbook option boWriteZoomFactor which must be included if you want to write the zoom factor to file. To be honest, I cannot remember why I gave this property such a special treatment... Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     sheet.ZoomFactor := 2.0;    book.Options := book.Options + [boWriteZoomFactor];  Thanks for the explanation! I'm going to change my program to include boWriteZoomFactor in the Workbook options. If I may give you some advice, this option could be enabled by default, since it is normal behavior.  Quote from: wp on May 22, 2025, 04:19:23 pmImplemented reading of one- cell-anchor images, as well as image rotation angles from xlsx files, based on your patch.   Thanks! I'm going to check the modified files in svn and include them in my installation. I know there is no forecast for this version to be released in OPM yet, but for now I'm going to use the svn version. I'm happy to have been able to make a small contribution to this library, which has been very useful to me. TQVM Roni