[{"data":1,"prerenderedAt":2651},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fresize-and-position-images-in-excel-with-openpyxl":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fresize-and-position-images-in-excel-with-openpyxl":2643},{"id":4,"title":5,"body":6,"dateModified":2621,"datePublished":2621,"description":2622,"extension":2623,"faq":2624,"meta":2635,"navigation":232,"path":2636,"seo":2637,"slug":2639,"stem":2640,"type":2641,"__hash__":2642},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fresize-and-position-images-in-excel-with-openpyxl\u002Findex.md","Resize and Position Images in Excel with openpyxl",{"type":7,"value":8,"toc":2609},"minimark",[9,19,150,155,184,191,318,321,325,340,638,641,648,711,714,718,725,1046,1070,1079,1085,1243,1246,1250,1253,1332,1749,1756,1760,1838,1845,1909,1912,1942,1945,2165,2169,2310,2314,2317,2328,2465,2471,2481,2492,2496,2508,2512,2518,2529,2538,2560,2566,2570,2605],[10,11,12,13,18],"p",{},"A logo in the wrong place makes a generated report look generated. openpyxl inserts images in two lines, and then the questions start: how big, measured in what, anchored to what, and why did it jump three columns when the widths changed? The answers come down to pixels versus EMU and to which anchor type you choose. This guide covers sizing without distortion, precise placement, and the round-trip behaviour that quietly drops images from a template. It extends ",[14,15,17],"a",{"href":16},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002F","Inserting Images and Logos into Excel",".",[20,21,30,31,30,35,30,39,30,46,30,55,30,62,30,70,30,73,30,77,30,83,30,89,30,94,30,99,30,103,30,108,30,113,30,118,30,122,30,125,30,128,30,133,30,137,30,140,30,143,30,146],"svg",{"viewBox":22,"role":23,"ariaLabel":24,"ariaLabelledBy":25,"xmlns":28,"style":29},"0 0 800 258","img","Two anchor types: a one-cell anchor fixes the image size and lets it move with its cell, while a two-cell anchor stretches the image between two cells so it resizes with the layout.",[26,27],"anchor-t","anchor-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:800px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[32,33,34],"title",{"id":26},"One-cell and two-cell anchors behave differently when the layout changes",[36,37,38],"desc",{"id":27},"A one-cell anchor pins the image's top-left corner to a cell and gives it a fixed extent, so widening a column moves the image but never changes its size — which is what a logo needs. A two-cell anchor pins opposite corners to two different cells, so the image stretches or shrinks as those cells move, which suits a chart image meant to fill a region but distorts a logo.",[40,41],"rect",{"x":42,"y":42,"width":43,"height":44,"fill":45},"0","800","258","#ffffff",[40,47],{"x":48,"y":49,"width":50,"height":51,"rx":48,"fill":52,"stroke":53,"style":54},"14","26","368","212","#d9f4f1","var(--teal,#0f9488)","stroke-width:2px",[56,57,61],"text",{"x":58,"y":59,"style":60},"198","54","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","OneCellAnchor",[40,63],{"x":64,"y":65,"width":66,"height":67,"rx":68,"fill":45,"stroke":69},"46","70","90","60","4","var(--line,#cdd5e6)",[40,71],{"x":72,"y":65,"width":66,"height":67,"rx":68,"fill":45,"stroke":69},"140",[40,74],{"x":75,"y":65,"width":76,"height":67,"rx":68,"fill":45,"stroke":69},"234","116",[40,78],{"x":79,"y":80,"width":65,"height":81,"rx":68,"fill":82},"56","80","40","#5b5cf0",[56,84,88],{"x":85,"y":86,"style":87},"91","105","font-size:9.5px;font-weight:700;fill:#ffffff;text-anchor:middle","logo",[56,90,93],{"x":58,"y":91,"style":92},"158","font-size:11px;fill:var(--text,#172033);text-anchor:middle","one corner pinned, fixed extent",[56,95,98],{"x":58,"y":96,"style":97},"182","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","widening a column moves it",[56,100,102],{"x":58,"y":101,"style":97},"202","but never resizes it",[56,104,107],{"x":58,"y":105,"style":106},"226","font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","right for a logo",[40,109],{"x":110,"y":49,"width":50,"height":51,"rx":48,"fill":111,"stroke":112,"style":54},"418","#ebebfd","var(--brand,#5b5cf0)",[56,114,117],{"x":115,"y":59,"style":116},"602","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","TwoCellAnchor",[40,119],{"x":120,"y":121,"width":66,"height":49,"rx":68,"fill":45,"stroke":69},"450","66",[40,123],{"x":124,"y":121,"width":66,"height":49,"rx":68,"fill":45,"stroke":69},"544",[40,126],{"x":127,"y":121,"width":76,"height":49,"rx":68,"fill":45,"stroke":69},"638",[40,129],{"x":120,"y":130,"width":131,"height":132,"rx":68,"fill":82},"98","304","34",[56,134,136],{"x":115,"y":135,"style":87},"120","stretched between two cells",[56,138,139],{"x":115,"y":91,"style":92},"both corners pinned",[56,141,142],{"x":115,"y":96,"style":97},"widening a column stretches it",[56,144,145],{"x":115,"y":101,"style":97},"aspect ratio is not preserved",[56,147,149],{"x":115,"y":105,"style":148},"font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","right for filling a region",[151,152,154],"h2",{"id":153},"prerequisites","Prerequisites",[156,157,162],"pre",{"className":158,"code":159,"language":160,"meta":161,"style":161},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl pillow\n","bash","",[163,164,165],"code",{"__ignoreMap":161},[166,167,170,174,178,181],"span",{"class":168,"line":169},"line",1,[166,171,173],{"class":172},"sMTad","pip",[166,175,177],{"class":176},"srMev"," install",[166,179,180],{"class":176}," openpyxl",[166,182,183],{"class":176}," pillow\n",[10,185,186,187,190],{},"Pillow is not optional here — openpyxl uses it to read the image's dimensions, and without it ",[163,188,189],{},"Image"," raises on anything but the simplest case.",[156,192,196],{"className":193,"code":194,"language":195,"meta":161,"style":161},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\nfrom openpyxl.drawing.image import Image\n\nwb = Workbook()\nws = wb.active\nws[\"A1\"] = \"Regional Revenue Report\"\n\nimg = Image(\"logo.png\")\nws.add_image(img, \"E1\")\nwb.save(\"report.xlsx\")\n","python",[163,197,198,214,227,234,246,257,274,279,296,307],{"__ignoreMap":161},[166,199,200,204,208,211],{"class":168,"line":169},[166,201,203],{"class":202},"s-kum","from",[166,205,207],{"class":206},"skGVy"," openpyxl ",[166,209,210],{"class":202},"import",[166,212,213],{"class":206}," Workbook\n",[166,215,217,219,222,224],{"class":168,"line":216},2,[166,218,203],{"class":202},[166,220,221],{"class":206}," openpyxl.drawing.image ",[166,223,210],{"class":202},[166,225,226],{"class":206}," Image\n",[166,228,230],{"class":168,"line":229},3,[166,231,233],{"emptyLinePlaceholder":232},true,"\n",[166,235,237,240,243],{"class":168,"line":236},4,[166,238,239],{"class":206},"wb ",[166,241,242],{"class":202},"=",[166,244,245],{"class":206}," Workbook()\n",[166,247,249,252,254],{"class":168,"line":248},5,[166,250,251],{"class":206},"ws ",[166,253,242],{"class":202},[166,255,256],{"class":206}," wb.active\n",[166,258,260,263,266,269,271],{"class":168,"line":259},6,[166,261,262],{"class":206},"ws[",[166,264,265],{"class":176},"\"A1\"",[166,267,268],{"class":206},"] ",[166,270,242],{"class":202},[166,272,273],{"class":176}," \"Regional Revenue Report\"\n",[166,275,277],{"class":168,"line":276},7,[166,278,233],{"emptyLinePlaceholder":232},[166,280,282,285,287,290,293],{"class":168,"line":281},8,[166,283,284],{"class":206},"img ",[166,286,242],{"class":202},[166,288,289],{"class":206}," Image(",[166,291,292],{"class":176},"\"logo.png\"",[166,294,295],{"class":206},")\n",[166,297,299,302,305],{"class":168,"line":298},9,[166,300,301],{"class":206},"ws.add_image(img, ",[166,303,304],{"class":176},"\"E1\"",[166,306,295],{"class":206},[166,308,310,313,316],{"class":168,"line":309},10,[166,311,312],{"class":206},"wb.save(",[166,314,315],{"class":176},"\"report.xlsx\"",[166,317,295],{"class":206},[10,319,320],{},"That inserts the image at its natural size, top-left corner in cell E1. Everything else is refinement.",[151,322,324],{"id":323},"step-1-size-without-distortion","Step 1 — Size without distortion",[10,326,327,330,331,334,335,339],{},[163,328,329],{},"width"," and ",[163,332,333],{},"height"," are in ",[336,337,338],"strong",{},"pixels at 96 dpi",". Setting both independently stretches the image, so compute one from the other:",[156,341,343],{"className":193,"code":342,"language":195,"meta":161,"style":161},"from openpyxl.drawing.image import Image\nfrom PIL import Image as PILImage\n\ndef sized_image(path, width=None, height=None):\n    \"\"\"Load an image scaled to a target width or height, preserving proportions.\"\"\"\n    if width is None and height is None:\n        return Image(path)\n\n    with PILImage.open(path) as source:\n        native_w, native_h = source.size\n\n    ratio = native_h \u002F native_w\n    if width is not None:\n        target_w, target_h = width, round(width * ratio)\n    else:\n        target_h, target_w = height, round(height \u002F ratio)\n\n    img = Image(path)\n    img.width, img.height = target_w, target_h\n    return img\n\nlogo = sized_image(\"logo.png\", width=180)\nws.add_image(logo, \"E1\")\n",[163,344,345,355,375,379,406,411,438,446,450,463,473,478,495,511,534,542,562,567,577,588,597,602,628],{"__ignoreMap":161},[166,346,347,349,351,353],{"class":168,"line":169},[166,348,203],{"class":202},[166,350,221],{"class":206},[166,352,210],{"class":202},[166,354,226],{"class":206},[166,356,357,359,363,366,369,372],{"class":168,"line":216},[166,358,203],{"class":202},[166,360,362],{"class":361},"sP0c6"," PIL",[166,364,365],{"class":202}," import",[166,367,368],{"class":206}," Image ",[166,370,371],{"class":202},"as",[166,373,374],{"class":206}," PILImage\n",[166,376,377],{"class":168,"line":229},[166,378,233],{"emptyLinePlaceholder":232},[166,380,381,384,388,391,393,396,399,401,403],{"class":168,"line":236},[166,382,383],{"class":202},"def",[166,385,387],{"class":386},"s_Opv"," sized_image",[166,389,390],{"class":206},"(path, width",[166,392,242],{"class":202},[166,394,395],{"class":361},"None",[166,397,398],{"class":206},", height",[166,400,242],{"class":202},[166,402,395],{"class":361},[166,404,405],{"class":206},"):\n",[166,407,408],{"class":168,"line":248},[166,409,410],{"class":176},"    \"\"\"Load an image scaled to a target width or height, preserving proportions.\"\"\"\n",[166,412,413,416,419,422,425,428,431,433,435],{"class":168,"line":259},[166,414,415],{"class":202},"    if",[166,417,418],{"class":206}," width ",[166,420,421],{"class":202},"is",[166,423,424],{"class":361}," None",[166,426,427],{"class":202}," and",[166,429,430],{"class":206}," height ",[166,432,421],{"class":202},[166,434,424],{"class":361},[166,436,437],{"class":206},":\n",[166,439,440,443],{"class":168,"line":276},[166,441,442],{"class":202},"        return",[166,444,445],{"class":206}," Image(path)\n",[166,447,448],{"class":168,"line":281},[166,449,233],{"emptyLinePlaceholder":232},[166,451,452,455,458,460],{"class":168,"line":298},[166,453,454],{"class":202},"    with",[166,456,457],{"class":206}," PILImage.open(path) ",[166,459,371],{"class":202},[166,461,462],{"class":206}," source:\n",[166,464,465,468,470],{"class":168,"line":309},[166,466,467],{"class":206},"        native_w, native_h ",[166,469,242],{"class":202},[166,471,472],{"class":206}," source.size\n",[166,474,476],{"class":168,"line":475},11,[166,477,233],{"emptyLinePlaceholder":232},[166,479,481,484,486,489,492],{"class":168,"line":480},12,[166,482,483],{"class":206},"    ratio ",[166,485,242],{"class":202},[166,487,488],{"class":206}," native_h ",[166,490,491],{"class":202},"\u002F",[166,493,494],{"class":206}," native_w\n",[166,496,498,500,502,504,507,509],{"class":168,"line":497},13,[166,499,415],{"class":202},[166,501,418],{"class":206},[166,503,421],{"class":202},[166,505,506],{"class":202}," not",[166,508,424],{"class":361},[166,510,437],{"class":206},[166,512,514,517,519,522,525,528,531],{"class":168,"line":513},14,[166,515,516],{"class":206},"        target_w, target_h ",[166,518,242],{"class":202},[166,520,521],{"class":206}," width, ",[166,523,524],{"class":361},"round",[166,526,527],{"class":206},"(width ",[166,529,530],{"class":202},"*",[166,532,533],{"class":206}," ratio)\n",[166,535,537,540],{"class":168,"line":536},15,[166,538,539],{"class":202},"    else",[166,541,437],{"class":206},[166,543,545,548,550,553,555,558,560],{"class":168,"line":544},16,[166,546,547],{"class":206},"        target_h, target_w ",[166,549,242],{"class":202},[166,551,552],{"class":206}," height, ",[166,554,524],{"class":361},[166,556,557],{"class":206},"(height ",[166,559,491],{"class":202},[166,561,533],{"class":206},[166,563,565],{"class":168,"line":564},17,[166,566,233],{"emptyLinePlaceholder":232},[166,568,570,573,575],{"class":168,"line":569},18,[166,571,572],{"class":206},"    img ",[166,574,242],{"class":202},[166,576,445],{"class":206},[166,578,580,583,585],{"class":168,"line":579},19,[166,581,582],{"class":206},"    img.width, img.height ",[166,584,242],{"class":202},[166,586,587],{"class":206}," target_w, target_h\n",[166,589,591,594],{"class":168,"line":590},20,[166,592,593],{"class":202},"    return",[166,595,596],{"class":206}," img\n",[166,598,600],{"class":168,"line":599},21,[166,601,233],{"emptyLinePlaceholder":232},[166,603,605,608,610,613,615,618,621,623,626],{"class":168,"line":604},22,[166,606,607],{"class":206},"logo ",[166,609,242],{"class":202},[166,611,612],{"class":206}," sized_image(",[166,614,292],{"class":176},[166,616,617],{"class":206},", ",[166,619,329],{"class":620},"sa561",[166,622,242],{"class":202},[166,624,625],{"class":361},"180",[166,627,295],{"class":206},[166,629,631,634,636],{"class":168,"line":630},23,[166,632,633],{"class":206},"ws.add_image(logo, ",[166,635,304],{"class":176},[166,637,295],{"class":206},[10,639,640],{},"Passing one dimension and deriving the other is the whole discipline. A logo squashed by ten per cent is the kind of thing nobody can name but everybody notices.",[10,642,643,644,647],{},"Make the row tall enough, or the image overlaps the data below it. Row height is in ",[336,645,646],{},"points",", not pixels, and the conversion is 0.75 points per pixel:",[156,649,651],{"className":193,"code":650,"language":195,"meta":161,"style":161},"PX_TO_POINTS = 0.75\n\ndef fit_row_to_image(ws, row, image, padding_px=8):\n    \"\"\"Set a row's height so an image sits inside it.\"\"\"\n    ws.row_dimensions[row].height = (image.height + padding_px) * PX_TO_POINTS\n",[163,652,653,664,668,685,690],{"__ignoreMap":161},[166,654,655,658,661],{"class":168,"line":169},[166,656,657],{"class":361},"PX_TO_POINTS",[166,659,660],{"class":202}," =",[166,662,663],{"class":361}," 0.75\n",[166,665,666],{"class":168,"line":216},[166,667,233],{"emptyLinePlaceholder":232},[166,669,670,672,675,678,680,683],{"class":168,"line":229},[166,671,383],{"class":202},[166,673,674],{"class":386}," fit_row_to_image",[166,676,677],{"class":206},"(ws, row, image, padding_px",[166,679,242],{"class":202},[166,681,682],{"class":361},"8",[166,684,405],{"class":206},[166,686,687],{"class":168,"line":236},[166,688,689],{"class":176},"    \"\"\"Set a row's height so an image sits inside it.\"\"\"\n",[166,691,692,695,697,700,703,706,708],{"class":168,"line":248},[166,693,694],{"class":206},"    ws.row_dimensions[row].height ",[166,696,242],{"class":202},[166,698,699],{"class":206}," (image.height ",[166,701,702],{"class":202},"+",[166,704,705],{"class":206}," padding_px) ",[166,707,530],{"class":202},[166,709,710],{"class":361}," PX_TO_POINTS\n",[10,712,713],{},"Three different units in play — pixels for images, points for row heights, characters for column widths — is genuinely confusing, and it is why placement often needs one round of trial and error.",[151,715,717],{"id":716},"step-2-anchor-precisely","Step 2 — Anchor precisely",[10,719,720,721,724],{},"A plain cell reference pins the top-left corner to that cell's top-left corner. For finer control, build the anchor yourself. Offsets are in ",[336,722,723],{},"EMU",", where 9,525 EMU is one pixel:",[156,726,728],{"className":193,"code":727,"language":195,"meta":161,"style":161},"from openpyxl.drawing.spreadsheet_drawing import (\n    AnchorMarker, OneCellAnchor,\n)\nfrom openpyxl.drawing.xdr import XDRPositiveSize2D\nfrom openpyxl.utils.units import pixels_to_EMU\n\nEMU_PER_PIXEL = 9525\n\ndef anchored_image(path, col, row, width=None, height=None,\n                   col_offset_px=0, row_offset_px=0):\n    \"\"\"An image at an exact offset inside a cell, with a fixed size.\"\"\"\n    img = sized_image(path, width=width, height=height)\n\n    marker = AnchorMarker(\n        col=col, row=row,                       # zero-based\n        colOff=pixels_to_EMU(col_offset_px),\n        rowOff=pixels_to_EMU(row_offset_px),\n    )\n    img.anchor = OneCellAnchor(\n        _from=marker,\n        ext=XDRPositiveSize2D(\n            pixels_to_EMU(img.width), pixels_to_EMU(img.height)\n        ),\n    )\n    return img\n\n# 12 pixels in and 6 down from the top-left of E2 (col 4, row 1 zero-based).\nws.add_image(anchored_image(\"logo.png\", col=4, row=1, width=180,\n                            col_offset_px=12, row_offset_px=6))\n",[163,729,730,742,747,751,763,775,779,789,793,816,834,839,862,866,876,898,908,918,923,933,943,953,958,963,968,975,980,986,1022],{"__ignoreMap":161},[166,731,732,734,737,739],{"class":168,"line":169},[166,733,203],{"class":202},[166,735,736],{"class":206}," openpyxl.drawing.spreadsheet_drawing ",[166,738,210],{"class":202},[166,740,741],{"class":206}," (\n",[166,743,744],{"class":168,"line":216},[166,745,746],{"class":206},"    AnchorMarker, OneCellAnchor,\n",[166,748,749],{"class":168,"line":229},[166,750,295],{"class":206},[166,752,753,755,758,760],{"class":168,"line":236},[166,754,203],{"class":202},[166,756,757],{"class":206}," openpyxl.drawing.xdr ",[166,759,210],{"class":202},[166,761,762],{"class":206}," XDRPositiveSize2D\n",[166,764,765,767,770,772],{"class":168,"line":248},[166,766,203],{"class":202},[166,768,769],{"class":206}," openpyxl.utils.units ",[166,771,210],{"class":202},[166,773,774],{"class":206}," pixels_to_EMU\n",[166,776,777],{"class":168,"line":259},[166,778,233],{"emptyLinePlaceholder":232},[166,780,781,784,786],{"class":168,"line":276},[166,782,783],{"class":361},"EMU_PER_PIXEL",[166,785,660],{"class":202},[166,787,788],{"class":361}," 9525\n",[166,790,791],{"class":168,"line":281},[166,792,233],{"emptyLinePlaceholder":232},[166,794,795,797,800,803,805,807,809,811,813],{"class":168,"line":298},[166,796,383],{"class":202},[166,798,799],{"class":386}," anchored_image",[166,801,802],{"class":206},"(path, col, row, width",[166,804,242],{"class":202},[166,806,395],{"class":361},[166,808,398],{"class":206},[166,810,242],{"class":202},[166,812,395],{"class":361},[166,814,815],{"class":206},",\n",[166,817,818,821,823,825,828,830,832],{"class":168,"line":309},[166,819,820],{"class":206},"                   col_offset_px",[166,822,242],{"class":202},[166,824,42],{"class":361},[166,826,827],{"class":206},", row_offset_px",[166,829,242],{"class":202},[166,831,42],{"class":361},[166,833,405],{"class":206},[166,835,836],{"class":168,"line":475},[166,837,838],{"class":176},"    \"\"\"An image at an exact offset inside a cell, with a fixed size.\"\"\"\n",[166,840,841,843,845,848,850,852,855,857,859],{"class":168,"line":480},[166,842,572],{"class":206},[166,844,242],{"class":202},[166,846,847],{"class":206}," sized_image(path, ",[166,849,329],{"class":620},[166,851,242],{"class":202},[166,853,854],{"class":206},"width, ",[166,856,333],{"class":620},[166,858,242],{"class":202},[166,860,861],{"class":206},"height)\n",[166,863,864],{"class":168,"line":497},[166,865,233],{"emptyLinePlaceholder":232},[166,867,868,871,873],{"class":168,"line":513},[166,869,870],{"class":206},"    marker ",[166,872,242],{"class":202},[166,874,875],{"class":206}," AnchorMarker(\n",[166,877,878,881,883,886,889,891,894],{"class":168,"line":536},[166,879,880],{"class":620},"        col",[166,882,242],{"class":202},[166,884,885],{"class":206},"col, ",[166,887,888],{"class":620},"row",[166,890,242],{"class":202},[166,892,893],{"class":206},"row,                       ",[166,895,897],{"class":896},"s-wDw","# zero-based\n",[166,899,900,903,905],{"class":168,"line":544},[166,901,902],{"class":620},"        colOff",[166,904,242],{"class":202},[166,906,907],{"class":206},"pixels_to_EMU(col_offset_px),\n",[166,909,910,913,915],{"class":168,"line":564},[166,911,912],{"class":620},"        rowOff",[166,914,242],{"class":202},[166,916,917],{"class":206},"pixels_to_EMU(row_offset_px),\n",[166,919,920],{"class":168,"line":569},[166,921,922],{"class":206},"    )\n",[166,924,925,928,930],{"class":168,"line":579},[166,926,927],{"class":206},"    img.anchor ",[166,929,242],{"class":202},[166,931,932],{"class":206}," OneCellAnchor(\n",[166,934,935,938,940],{"class":168,"line":590},[166,936,937],{"class":620},"        _from",[166,939,242],{"class":202},[166,941,942],{"class":206},"marker,\n",[166,944,945,948,950],{"class":168,"line":599},[166,946,947],{"class":620},"        ext",[166,949,242],{"class":202},[166,951,952],{"class":206},"XDRPositiveSize2D(\n",[166,954,955],{"class":168,"line":604},[166,956,957],{"class":206},"            pixels_to_EMU(img.width), pixels_to_EMU(img.height)\n",[166,959,960],{"class":168,"line":630},[166,961,962],{"class":206},"        ),\n",[166,964,966],{"class":168,"line":965},24,[166,967,922],{"class":206},[166,969,971,973],{"class":168,"line":970},25,[166,972,593],{"class":202},[166,974,596],{"class":206},[166,976,978],{"class":168,"line":977},26,[166,979,233],{"emptyLinePlaceholder":232},[166,981,983],{"class":168,"line":982},27,[166,984,985],{"class":896},"# 12 pixels in and 6 down from the top-left of E2 (col 4, row 1 zero-based).\n",[166,987,989,992,994,996,999,1001,1003,1005,1007,1009,1012,1014,1016,1018,1020],{"class":168,"line":988},28,[166,990,991],{"class":206},"ws.add_image(anchored_image(",[166,993,292],{"class":176},[166,995,617],{"class":206},[166,997,998],{"class":620},"col",[166,1000,242],{"class":202},[166,1002,68],{"class":361},[166,1004,617],{"class":206},[166,1006,888],{"class":620},[166,1008,242],{"class":202},[166,1010,1011],{"class":361},"1",[166,1013,617],{"class":206},[166,1015,329],{"class":620},[166,1017,242],{"class":202},[166,1019,625],{"class":361},[166,1021,815],{"class":206},[166,1023,1025,1028,1030,1033,1035,1038,1040,1043],{"class":168,"line":1024},29,[166,1026,1027],{"class":620},"                            col_offset_px",[166,1029,242],{"class":202},[166,1031,1032],{"class":361},"12",[166,1034,617],{"class":206},[166,1036,1037],{"class":620},"row_offset_px",[166,1039,242],{"class":202},[166,1041,1042],{"class":361},"6",[166,1044,1045],{"class":206},"))\n",[10,1047,1048,1049,1052,1053,330,1055,1057,1058,1061,1062,1065,1066,1069],{},"Note the ",[336,1050,1051],{},"zero-based"," ",[163,1054,998],{},[163,1056,888],{}," in ",[163,1059,1060],{},"AnchorMarker",", against openpyxl's one-based cell addressing everywhere else. Column E is ",[163,1063,1064],{},"col=4",", row 2 is ",[163,1067,1068],{},"row=1",". Getting this wrong shifts the image one cell up and left, which looks like an off-by-one because it is one.",[10,1071,1072,1074,1075,1078],{},[163,1073,61],{}," with an explicit ",[163,1076,1077],{},"ext"," is what keeps the size fixed. The image still moves if the anchoring cell moves, but it never stretches — which is the behaviour a logo wants.",[10,1080,1081,1082,1084],{},"For an image that should fill a region and resize with it, use ",[163,1083,117],{}," instead:",[156,1086,1088],{"className":193,"code":1087,"language":195,"meta":161,"style":161},"from openpyxl.drawing.spreadsheet_drawing import AnchorMarker, TwoCellAnchor\n\ndef stretched_image(path, from_col, from_row, to_col, to_row):\n    \"\"\"An image stretched between two cells — resizes with the layout.\"\"\"\n    img = Image(path)\n    img.anchor = TwoCellAnchor(\n        editAs=\"twoCell\",\n        _from=AnchorMarker(col=from_col, row=from_row),\n        to=AnchorMarker(col=to_col, row=to_row),\n    )\n    return img\n\n# Fill the block from E10 to K24.\nws.add_image(stretched_image(\"chart.png\", 4, 9, 10, 23))\n",[163,1089,1090,1101,1105,1115,1120,1128,1137,1149,1172,1195,1199,1205,1209,1214],{"__ignoreMap":161},[166,1091,1092,1094,1096,1098],{"class":168,"line":169},[166,1093,203],{"class":202},[166,1095,736],{"class":206},[166,1097,210],{"class":202},[166,1099,1100],{"class":206}," AnchorMarker, TwoCellAnchor\n",[166,1102,1103],{"class":168,"line":216},[166,1104,233],{"emptyLinePlaceholder":232},[166,1106,1107,1109,1112],{"class":168,"line":229},[166,1108,383],{"class":202},[166,1110,1111],{"class":386}," stretched_image",[166,1113,1114],{"class":206},"(path, from_col, from_row, to_col, to_row):\n",[166,1116,1117],{"class":168,"line":236},[166,1118,1119],{"class":176},"    \"\"\"An image stretched between two cells — resizes with the layout.\"\"\"\n",[166,1121,1122,1124,1126],{"class":168,"line":248},[166,1123,572],{"class":206},[166,1125,242],{"class":202},[166,1127,445],{"class":206},[166,1129,1130,1132,1134],{"class":168,"line":259},[166,1131,927],{"class":206},[166,1133,242],{"class":202},[166,1135,1136],{"class":206}," TwoCellAnchor(\n",[166,1138,1139,1142,1144,1147],{"class":168,"line":276},[166,1140,1141],{"class":620},"        editAs",[166,1143,242],{"class":202},[166,1145,1146],{"class":176},"\"twoCell\"",[166,1148,815],{"class":206},[166,1150,1151,1153,1155,1158,1160,1162,1165,1167,1169],{"class":168,"line":281},[166,1152,937],{"class":620},[166,1154,242],{"class":202},[166,1156,1157],{"class":206},"AnchorMarker(",[166,1159,998],{"class":620},[166,1161,242],{"class":202},[166,1163,1164],{"class":206},"from_col, ",[166,1166,888],{"class":620},[166,1168,242],{"class":202},[166,1170,1171],{"class":206},"from_row),\n",[166,1173,1174,1177,1179,1181,1183,1185,1188,1190,1192],{"class":168,"line":298},[166,1175,1176],{"class":620},"        to",[166,1178,242],{"class":202},[166,1180,1157],{"class":206},[166,1182,998],{"class":620},[166,1184,242],{"class":202},[166,1186,1187],{"class":206},"to_col, ",[166,1189,888],{"class":620},[166,1191,242],{"class":202},[166,1193,1194],{"class":206},"to_row),\n",[166,1196,1197],{"class":168,"line":309},[166,1198,922],{"class":206},[166,1200,1201,1203],{"class":168,"line":475},[166,1202,593],{"class":202},[166,1204,596],{"class":206},[166,1206,1207],{"class":168,"line":480},[166,1208,233],{"emptyLinePlaceholder":232},[166,1210,1211],{"class":168,"line":497},[166,1212,1213],{"class":896},"# Fill the block from E10 to K24.\n",[166,1215,1216,1219,1222,1224,1226,1228,1231,1233,1236,1238,1241],{"class":168,"line":513},[166,1217,1218],{"class":206},"ws.add_image(stretched_image(",[166,1220,1221],{"class":176},"\"chart.png\"",[166,1223,617],{"class":206},[166,1225,68],{"class":361},[166,1227,617],{"class":206},[166,1229,1230],{"class":361},"9",[166,1232,617],{"class":206},[166,1234,1235],{"class":361},"10",[166,1237,617],{"class":206},[166,1239,1240],{"class":361},"23",[166,1242,1045],{"class":206},[10,1244,1245],{},"That is right for a rendered chart image meant to occupy a panel, and wrong for a logo, because the aspect ratio follows the cells rather than the image.",[151,1247,1249],{"id":1248},"step-3-lay-out-several-images","Step 3 — Lay out several images",[10,1251,1252],{},"A report footer with several logos, or a gallery of rendered charts, needs positions computed rather than typed.",[20,1254,30,1260,30,1263,30,1266,30,1269,30,1275,30,1281,30,1284,30,1290,30,1295,30,1297,30,1300,30,1303,30,1306,30,1310,30,1313,30,1316,30,1320,30,1323,30,1328],{"viewBox":1255,"role":23,"ariaLabel":1256,"ariaLabelledBy":1257,"xmlns":28,"style":29},"0 0 800 232","Computed image layout: a fixed starting cell, a per-image column stride, and an offset produce evenly spaced placements without hand-typed cell references.",[1258,1259],"layout-t","layout-d",[32,1261,1262],{"id":1258},"Computing positions instead of typing cell references",[36,1264,1265],{"id":1259},"Four images placed across a sheet. A starting column and row fix the first position, a stride in columns sets the spacing, and each image's index multiplies the stride. Adding a fifth image needs no new cell reference, and changing the spacing is one number rather than four edits. The same arithmetic drives a grid by taking the quotient and remainder of the index against a column count.",[40,1267],{"x":42,"y":42,"width":43,"height":1268,"fill":45},"232",[56,1270,1274],{"x":1271,"y":1272,"style":1273},"400","28","font-size:12px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","start column 1, stride 4 columns",[40,1276],{"x":1277,"y":64,"width":1278,"height":1279,"rx":1235,"fill":1280,"stroke":69,"style":54},"30","740","96","#f0f2f5",[40,1282],{"x":1283,"y":121,"width":72,"height":79,"rx":1042,"fill":82},"52",[56,1285,1289],{"x":1286,"y":1287,"style":1288},"122","99","font-size:10.5px;font-weight:700;fill:#ffffff;text-anchor:middle","image 0",[56,1291,1294],{"x":1286,"y":1292,"style":1293},"160","font-size:10px;fill:var(--muted,#5b6780);text-anchor:middle","col 1",[40,1296],{"x":75,"y":121,"width":72,"height":79,"rx":1042,"fill":82},[56,1298,1299],{"x":131,"y":1287,"style":1288},"image 1",[56,1301,1302],{"x":131,"y":1292,"style":1293},"col 5",[40,1304],{"x":1305,"y":121,"width":72,"height":79,"rx":1042,"fill":82},"416",[56,1307,1309],{"x":1308,"y":1287,"style":1288},"486","image 2",[56,1311,1312],{"x":1308,"y":1292,"style":1293},"col 9",[40,1314],{"x":1315,"y":121,"width":72,"height":79,"rx":1042,"fill":82},"598",[56,1317,1319],{"x":1318,"y":1287,"style":1288},"668","image 3",[56,1321,1322],{"x":1318,"y":1292,"style":1293},"col 13",[40,1324],{"x":1325,"y":96,"width":1326,"height":1327,"rx":1230,"fill":52,"stroke":53,"style":54},"150","500","36",[56,1329,1331],{"x":1271,"y":1330,"style":106},"205","col = start + index × stride — a fifth image needs no new reference",[156,1333,1335],{"className":193,"code":1334,"language":195,"meta":161,"style":161},"from openpyxl import Workbook\n\ndef place_row(ws, paths, start_col=1, start_row=1, stride=4, width=160,\n              gap_px=8):\n    \"\"\"Place images left to right, evenly spaced.\"\"\"\n    placed = []\n    for index, path in enumerate(paths):\n        img = anchored_image(\n            path,\n            col=start_col + index * stride,\n            row=start_row,\n            width=width,\n            col_offset_px=gap_px,\n            row_offset_px=gap_px,\n        )\n        ws.add_image(img)\n        placed.append(img)\n\n    if placed:\n        tallest = max(i.height for i in placed)\n        ws.row_dimensions[start_row + 1].height = (tallest + 2 * gap_px) * 0.75\n    return placed\n\ndef place_grid(ws, paths, columns=2, start_col=1, start_row=1,\n               col_stride=6, row_stride=16, width=280):\n    \"\"\"Place images in a grid — useful for a page of rendered charts.\"\"\"\n    for index, path in enumerate(paths):\n        down, across = divmod(index, columns)\n        ws.add_image(anchored_image(\n            path,\n            col=start_col + across * col_stride,\n            row=start_row + down * row_stride,\n            width=width,\n        ))\n",[163,1336,1337,1347,1351,1388,1399,1404,1414,1431,1441,1446,1466,1476,1486,1496,1505,1510,1515,1520,1524,1531,1555,1588,1595,1599,1629,1655,1660,1672,1685,1690,1695,1714,1734,1743],{"__ignoreMap":161},[166,1338,1339,1341,1343,1345],{"class":168,"line":169},[166,1340,203],{"class":202},[166,1342,207],{"class":206},[166,1344,210],{"class":202},[166,1346,213],{"class":206},[166,1348,1349],{"class":168,"line":216},[166,1350,233],{"emptyLinePlaceholder":232},[166,1352,1353,1355,1358,1361,1363,1365,1368,1370,1372,1375,1377,1379,1382,1384,1386],{"class":168,"line":229},[166,1354,383],{"class":202},[166,1356,1357],{"class":386}," place_row",[166,1359,1360],{"class":206},"(ws, paths, start_col",[166,1362,242],{"class":202},[166,1364,1011],{"class":361},[166,1366,1367],{"class":206},", start_row",[166,1369,242],{"class":202},[166,1371,1011],{"class":361},[166,1373,1374],{"class":206},", stride",[166,1376,242],{"class":202},[166,1378,68],{"class":361},[166,1380,1381],{"class":206},", width",[166,1383,242],{"class":202},[166,1385,1292],{"class":361},[166,1387,815],{"class":206},[166,1389,1390,1393,1395,1397],{"class":168,"line":236},[166,1391,1392],{"class":206},"              gap_px",[166,1394,242],{"class":202},[166,1396,682],{"class":361},[166,1398,405],{"class":206},[166,1400,1401],{"class":168,"line":248},[166,1402,1403],{"class":176},"    \"\"\"Place images left to right, evenly spaced.\"\"\"\n",[166,1405,1406,1409,1411],{"class":168,"line":259},[166,1407,1408],{"class":206},"    placed ",[166,1410,242],{"class":202},[166,1412,1413],{"class":206}," []\n",[166,1415,1416,1419,1422,1425,1428],{"class":168,"line":276},[166,1417,1418],{"class":202},"    for",[166,1420,1421],{"class":206}," index, path ",[166,1423,1424],{"class":202},"in",[166,1426,1427],{"class":361}," enumerate",[166,1429,1430],{"class":206},"(paths):\n",[166,1432,1433,1436,1438],{"class":168,"line":281},[166,1434,1435],{"class":206},"        img ",[166,1437,242],{"class":202},[166,1439,1440],{"class":206}," anchored_image(\n",[166,1442,1443],{"class":168,"line":298},[166,1444,1445],{"class":206},"            path,\n",[166,1447,1448,1451,1453,1456,1458,1461,1463],{"class":168,"line":309},[166,1449,1450],{"class":620},"            col",[166,1452,242],{"class":202},[166,1454,1455],{"class":206},"start_col ",[166,1457,702],{"class":202},[166,1459,1460],{"class":206}," index ",[166,1462,530],{"class":202},[166,1464,1465],{"class":206}," stride,\n",[166,1467,1468,1471,1473],{"class":168,"line":475},[166,1469,1470],{"class":620},"            row",[166,1472,242],{"class":202},[166,1474,1475],{"class":206},"start_row,\n",[166,1477,1478,1481,1483],{"class":168,"line":480},[166,1479,1480],{"class":620},"            width",[166,1482,242],{"class":202},[166,1484,1485],{"class":206},"width,\n",[166,1487,1488,1491,1493],{"class":168,"line":497},[166,1489,1490],{"class":620},"            col_offset_px",[166,1492,242],{"class":202},[166,1494,1495],{"class":206},"gap_px,\n",[166,1497,1498,1501,1503],{"class":168,"line":513},[166,1499,1500],{"class":620},"            row_offset_px",[166,1502,242],{"class":202},[166,1504,1495],{"class":206},[166,1506,1507],{"class":168,"line":536},[166,1508,1509],{"class":206},"        )\n",[166,1511,1512],{"class":168,"line":544},[166,1513,1514],{"class":206},"        ws.add_image(img)\n",[166,1516,1517],{"class":168,"line":564},[166,1518,1519],{"class":206},"        placed.append(img)\n",[166,1521,1522],{"class":168,"line":569},[166,1523,233],{"emptyLinePlaceholder":232},[166,1525,1526,1528],{"class":168,"line":579},[166,1527,415],{"class":202},[166,1529,1530],{"class":206}," placed:\n",[166,1532,1533,1536,1538,1541,1544,1547,1550,1552],{"class":168,"line":590},[166,1534,1535],{"class":206},"        tallest ",[166,1537,242],{"class":202},[166,1539,1540],{"class":361}," max",[166,1542,1543],{"class":206},"(i.height ",[166,1545,1546],{"class":202},"for",[166,1548,1549],{"class":206}," i ",[166,1551,1424],{"class":202},[166,1553,1554],{"class":206}," placed)\n",[166,1556,1557,1560,1562,1565,1568,1570,1573,1575,1578,1581,1584,1586],{"class":168,"line":599},[166,1558,1559],{"class":206},"        ws.row_dimensions[start_row ",[166,1561,702],{"class":202},[166,1563,1564],{"class":361}," 1",[166,1566,1567],{"class":206},"].height ",[166,1569,242],{"class":202},[166,1571,1572],{"class":206}," (tallest ",[166,1574,702],{"class":202},[166,1576,1577],{"class":361}," 2",[166,1579,1580],{"class":202}," *",[166,1582,1583],{"class":206}," gap_px) ",[166,1585,530],{"class":202},[166,1587,663],{"class":361},[166,1589,1590,1592],{"class":168,"line":604},[166,1591,593],{"class":202},[166,1593,1594],{"class":206}," placed\n",[166,1596,1597],{"class":168,"line":630},[166,1598,233],{"emptyLinePlaceholder":232},[166,1600,1601,1603,1606,1609,1611,1614,1617,1619,1621,1623,1625,1627],{"class":168,"line":965},[166,1602,383],{"class":202},[166,1604,1605],{"class":386}," place_grid",[166,1607,1608],{"class":206},"(ws, paths, columns",[166,1610,242],{"class":202},[166,1612,1613],{"class":361},"2",[166,1615,1616],{"class":206},", start_col",[166,1618,242],{"class":202},[166,1620,1011],{"class":361},[166,1622,1367],{"class":206},[166,1624,242],{"class":202},[166,1626,1011],{"class":361},[166,1628,815],{"class":206},[166,1630,1631,1634,1636,1638,1641,1643,1646,1648,1650,1653],{"class":168,"line":970},[166,1632,1633],{"class":206},"               col_stride",[166,1635,242],{"class":202},[166,1637,1042],{"class":361},[166,1639,1640],{"class":206},", row_stride",[166,1642,242],{"class":202},[166,1644,1645],{"class":361},"16",[166,1647,1381],{"class":206},[166,1649,242],{"class":202},[166,1651,1652],{"class":361},"280",[166,1654,405],{"class":206},[166,1656,1657],{"class":168,"line":977},[166,1658,1659],{"class":176},"    \"\"\"Place images in a grid — useful for a page of rendered charts.\"\"\"\n",[166,1661,1662,1664,1666,1668,1670],{"class":168,"line":982},[166,1663,1418],{"class":202},[166,1665,1421],{"class":206},[166,1667,1424],{"class":202},[166,1669,1427],{"class":361},[166,1671,1430],{"class":206},[166,1673,1674,1677,1679,1682],{"class":168,"line":988},[166,1675,1676],{"class":206},"        down, across ",[166,1678,242],{"class":202},[166,1680,1681],{"class":361}," divmod",[166,1683,1684],{"class":206},"(index, columns)\n",[166,1686,1687],{"class":168,"line":1024},[166,1688,1689],{"class":206},"        ws.add_image(anchored_image(\n",[166,1691,1693],{"class":168,"line":1692},30,[166,1694,1445],{"class":206},[166,1696,1698,1700,1702,1704,1706,1709,1711],{"class":168,"line":1697},31,[166,1699,1450],{"class":620},[166,1701,242],{"class":202},[166,1703,1455],{"class":206},[166,1705,702],{"class":202},[166,1707,1708],{"class":206}," across ",[166,1710,530],{"class":202},[166,1712,1713],{"class":206}," col_stride,\n",[166,1715,1717,1719,1721,1724,1726,1729,1731],{"class":168,"line":1716},32,[166,1718,1470],{"class":620},[166,1720,242],{"class":202},[166,1722,1723],{"class":206},"start_row ",[166,1725,702],{"class":202},[166,1727,1728],{"class":206}," down ",[166,1730,530],{"class":202},[166,1732,1733],{"class":206}," row_stride,\n",[166,1735,1737,1739,1741],{"class":168,"line":1736},33,[166,1738,1480],{"class":620},[166,1740,242],{"class":202},[166,1742,1485],{"class":206},[166,1744,1746],{"class":168,"line":1745},34,[166,1747,1748],{"class":206},"        ))\n",[10,1750,1751,1752,18],{},"Computing the positions means adding a fifth chart is a list entry rather than four new cell references, and changing the spacing is one number. That composes well with the matplotlib workflow in ",[14,1753,1755],{"href":1754},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-a-matplotlib-chart-in-an-excel-report\u002F","embedding a matplotlib chart in an Excel report",[151,1757,1759],{"id":1758},"step-4-remember-that-images-do-not-survive-a-round-trip","Step 4 — Remember that images do not survive a round trip",[20,1761,30,1767,30,1770,30,1773,30,1776,30,1780,30,1784,30,1789,30,1792,30,1796,30,1800,30,1805,30,1809,30,1811,30,1814,30,1817,30,1820,30,1823,30,1827,30,1829,30,1832,30,1835],{"viewBox":1762,"role":23,"ariaLabel":1763,"ariaLabelledBy":1764,"xmlns":28,"style":29},"0 0 800 224","Three units in play: images in pixels, row heights in points at three quarters of a pixel, and anchor offsets in EMU at 9,525 per pixel.",[1765,1766],"units-t","units-d",[32,1768,1769],{"id":1765},"Three different units for one placement",[36,1771,1772],{"id":1766},"Placing an image touches three unit systems. Image width and height are in pixels at ninety-six dots per inch. Row height is in points, at three quarters of a pixel, so a sixty-pixel image needs a forty-five point row. Anchor offsets are in EMU, English Metric Units, at nine thousand five hundred and twenty-five per pixel. Mixing them up is why placement usually takes one round of trial and error.",[40,1774],{"x":42,"y":42,"width":43,"height":1775,"fill":45},"224",[40,1777],{"x":48,"y":1277,"width":1778,"height":1779,"rx":48,"fill":111,"stroke":112,"style":54},"248","164",[56,1781,1783],{"x":1782,"y":67,"style":116},"138","image size",[56,1785,1788],{"x":1782,"y":1786,"style":1787},"92","font-size:14px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","pixels",[56,1790,1791],{"x":1782,"y":1286,"style":92},"img.width = 180",[56,1793,1795],{"x":1782,"y":1325,"style":1794},"font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:middle","at 96 dpi",[56,1797,1799],{"x":1782,"y":1798,"style":1794},"176","derive one from the other",[40,1801],{"x":1802,"y":1277,"width":1778,"height":1779,"rx":48,"fill":1803,"stroke":1804,"style":54},"276","#fdefd8","var(--gold,#b4740a)",[56,1806,1808],{"x":1271,"y":67,"style":1807},"font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","row height",[56,1810,646],{"x":1271,"y":1786,"style":1787},[56,1812,1813],{"x":1271,"y":1286,"style":92},"height = px × 0.75",[56,1815,1816],{"x":1271,"y":1325,"style":1794},"60 px → 45 points",[56,1818,1819],{"x":1271,"y":1798,"style":1794},"too short and it overlaps",[40,1821],{"x":1822,"y":1277,"width":1778,"height":1779,"rx":48,"fill":52,"stroke":53,"style":54},"538",[56,1824,1826],{"x":1825,"y":67,"style":60},"662","anchor offsets",[56,1828,723],{"x":1825,"y":1786,"style":1787},[56,1830,1831],{"x":1825,"y":1286,"style":92},"9,525 per pixel",[56,1833,1834],{"x":1825,"y":1325,"style":1794},"pixels_to_EMU(12)",[56,1836,1837],{"x":1825,"y":1798,"style":1794},"and the index is zero-based",[10,1839,1840,1841,1844],{},"This is the behaviour that costs the most time. openpyxl ",[336,1842,1843],{},"drops images that were already in a workbook"," when it loads it — they are not parsed into the object model, so they are not written back:",[156,1846,1848],{"className":193,"code":1847,"language":195,"meta":161,"style":161},"from openpyxl import load_workbook\n\nwb = load_workbook(\"template_with_logo.xlsx\")\nprint(len(wb.active._images))     # 0 — the logo is already gone\nwb.save(\"output.xlsx\")            # output has no logo\n",[163,1849,1850,1861,1865,1879,1896],{"__ignoreMap":161},[166,1851,1852,1854,1856,1858],{"class":168,"line":169},[166,1853,203],{"class":202},[166,1855,207],{"class":206},[166,1857,210],{"class":202},[166,1859,1860],{"class":206}," load_workbook\n",[166,1862,1863],{"class":168,"line":216},[166,1864,233],{"emptyLinePlaceholder":232},[166,1866,1867,1869,1871,1874,1877],{"class":168,"line":229},[166,1868,239],{"class":206},[166,1870,242],{"class":202},[166,1872,1873],{"class":206}," load_workbook(",[166,1875,1876],{"class":176},"\"template_with_logo.xlsx\"",[166,1878,295],{"class":206},[166,1880,1881,1884,1887,1890,1893],{"class":168,"line":236},[166,1882,1883],{"class":361},"print",[166,1885,1886],{"class":206},"(",[166,1888,1889],{"class":361},"len",[166,1891,1892],{"class":206},"(wb.active._images))     ",[166,1894,1895],{"class":896},"# 0 — the logo is already gone\n",[166,1897,1898,1900,1903,1906],{"class":168,"line":248},[166,1899,312],{"class":206},[166,1901,1902],{"class":176},"\"output.xlsx\"",[166,1904,1905],{"class":206},")            ",[166,1907,1908],{"class":896},"# output has no logo\n",[10,1910,1911],{},"There is no flag to change it. The practical consequences:",[1913,1914,1915,1922,1928],"ul",{},[1916,1917,1918,1921],"li",{},[336,1919,1920],{},"Do not rely on a template's logo."," Re-add every image your output needs, from files your script controls.",[1916,1923,1924,1927],{},[336,1925,1926],{},"Keep the image assets with the code",", so a report can always be rebuilt identically.",[1916,1929,1930,1933,1934,1937,1938,18],{},[336,1931,1932],{},"Use LibreOffice for a true copy"," when a template's images genuinely must survive untouched — ",[163,1935,1936],{},"soffice --headless --convert-to xlsx"," preserves them, as in ",[14,1939,1941],{"href":1940},"\u002Fgetting-started-with-python-excel-automation\u002Fhandling-excel-file-formats-and-conversions\u002Fconvert-xls-to-xlsx-with-python\u002F","converting .xls to .xlsx",[10,1943,1944],{},"A helper that always re-applies the branding makes this a non-issue:",[156,1946,1948],{"className":193,"code":1947,"language":195,"meta":161,"style":161},"from pathlib import Path\n\nBRANDING = Path(\"assets\")\n\ndef apply_branding(ws, logo=\"logo.png\", col=0, row=0, width=180):\n    \"\"\"Re-add the logo. Call on every generated workbook, always.\"\"\"\n    path = BRANDING \u002F logo\n    if not path.exists():\n        raise FileNotFoundError(f\"branding asset missing: {path}\")\n    ws.add_image(anchored_image(str(path), col=col, row=row, width=width,\n                                col_offset_px=8, row_offset_px=6))\n    ws.row_dimensions[row + 1].height = (width * 0.35 + 12) * 0.75\n",[163,1949,1950,1962,1966,1981,1985,2021,2026,2042,2051,2082,2112,2131],{"__ignoreMap":161},[166,1951,1952,1954,1957,1959],{"class":168,"line":169},[166,1953,203],{"class":202},[166,1955,1956],{"class":206}," pathlib ",[166,1958,210],{"class":202},[166,1960,1961],{"class":206}," Path\n",[166,1963,1964],{"class":168,"line":216},[166,1965,233],{"emptyLinePlaceholder":232},[166,1967,1968,1971,1973,1976,1979],{"class":168,"line":229},[166,1969,1970],{"class":361},"BRANDING",[166,1972,660],{"class":202},[166,1974,1975],{"class":206}," Path(",[166,1977,1978],{"class":176},"\"assets\"",[166,1980,295],{"class":206},[166,1982,1983],{"class":168,"line":236},[166,1984,233],{"emptyLinePlaceholder":232},[166,1986,1987,1989,1992,1995,1997,1999,2002,2004,2006,2009,2011,2013,2015,2017,2019],{"class":168,"line":248},[166,1988,383],{"class":202},[166,1990,1991],{"class":386}," apply_branding",[166,1993,1994],{"class":206},"(ws, logo",[166,1996,242],{"class":202},[166,1998,292],{"class":176},[166,2000,2001],{"class":206},", col",[166,2003,242],{"class":202},[166,2005,42],{"class":361},[166,2007,2008],{"class":206},", row",[166,2010,242],{"class":202},[166,2012,42],{"class":361},[166,2014,1381],{"class":206},[166,2016,242],{"class":202},[166,2018,625],{"class":361},[166,2020,405],{"class":206},[166,2022,2023],{"class":168,"line":259},[166,2024,2025],{"class":176},"    \"\"\"Re-add the logo. Call on every generated workbook, always.\"\"\"\n",[166,2027,2028,2031,2033,2036,2039],{"class":168,"line":276},[166,2029,2030],{"class":206},"    path ",[166,2032,242],{"class":202},[166,2034,2035],{"class":361}," BRANDING",[166,2037,2038],{"class":202}," \u002F",[166,2040,2041],{"class":206}," logo\n",[166,2043,2044,2046,2048],{"class":168,"line":281},[166,2045,415],{"class":202},[166,2047,506],{"class":202},[166,2049,2050],{"class":206}," path.exists():\n",[166,2052,2053,2056,2059,2061,2064,2067,2071,2074,2077,2080],{"class":168,"line":298},[166,2054,2055],{"class":202},"        raise",[166,2057,2058],{"class":361}," FileNotFoundError",[166,2060,1886],{"class":206},[166,2062,2063],{"class":202},"f",[166,2065,2066],{"class":176},"\"branding asset missing: ",[166,2068,2070],{"class":2069},"sSjpA","{",[166,2072,2073],{"class":206},"path",[166,2075,2076],{"class":2069},"}",[166,2078,2079],{"class":176},"\"",[166,2081,295],{"class":206},[166,2083,2084,2087,2090,2093,2095,2097,2099,2101,2103,2106,2108,2110],{"class":168,"line":309},[166,2085,2086],{"class":206},"    ws.add_image(anchored_image(",[166,2088,2089],{"class":361},"str",[166,2091,2092],{"class":206},"(path), ",[166,2094,998],{"class":620},[166,2096,242],{"class":202},[166,2098,885],{"class":206},[166,2100,888],{"class":620},[166,2102,242],{"class":202},[166,2104,2105],{"class":206},"row, ",[166,2107,329],{"class":620},[166,2109,242],{"class":202},[166,2111,1485],{"class":206},[166,2113,2114,2117,2119,2121,2123,2125,2127,2129],{"class":168,"line":475},[166,2115,2116],{"class":620},"                                col_offset_px",[166,2118,242],{"class":202},[166,2120,682],{"class":361},[166,2122,617],{"class":206},[166,2124,1037],{"class":620},[166,2126,242],{"class":202},[166,2128,1042],{"class":361},[166,2130,1045],{"class":206},[166,2132,2133,2136,2138,2140,2142,2144,2147,2149,2152,2155,2158,2161,2163],{"class":168,"line":480},[166,2134,2135],{"class":206},"    ws.row_dimensions[row ",[166,2137,702],{"class":202},[166,2139,1564],{"class":361},[166,2141,1567],{"class":206},[166,2143,242],{"class":202},[166,2145,2146],{"class":206}," (width ",[166,2148,530],{"class":202},[166,2150,2151],{"class":361}," 0.35",[166,2153,2154],{"class":202}," +",[166,2156,2157],{"class":361}," 12",[166,2159,2160],{"class":206},") ",[166,2162,530],{"class":202},[166,2164,663],{"class":361},[151,2166,2168],{"id":2167},"common-pitfalls-and-fixes","Common pitfalls and fixes",[2170,2171,2172,2188],"table",{},[2173,2174,2175],"thead",{},[2176,2177,2178,2182,2185],"tr",{},[2179,2180,2181],"th",{},"Symptom",[2179,2183,2184],{},"Cause",[2179,2186,2187],{},"Fix",[2189,2190,2191,2207,2218,2235,2252,2263,2281,2295],"tbody",{},[2176,2192,2193,2197,2204],{},[2194,2195,2196],"td",{},"Logo looks stretched",[2194,2198,2199,330,2201,2203],{},[163,2200,329],{},[163,2202,333],{}," set independently",[2194,2205,2206],{},"Derive one from the source aspect ratio.",[2176,2208,2209,2212,2215],{},[2194,2210,2211],{},"Image overlaps the data below",[2194,2213,2214],{},"Row too short",[2194,2216,2217],{},"Set the row height, remembering points ≠ pixels.",[2176,2219,2220,2223,2228],{},[2194,2221,2222],{},"Image one cell up and left",[2194,2224,2225,2227],{},[163,2226,1060],{}," is zero-based",[2194,2229,2230,2231,1065,2233,18],{},"Column E is ",[163,2232,1064],{},[163,2234,1068],{},[2176,2236,2237,2240,2245],{},[2194,2238,2239],{},"Image resizes when a column widens",[2194,2241,2242,2244],{},[163,2243,117],{}," used",[2194,2246,2247,2248,1074,2250,18],{},"Use ",[163,2249,61],{},[163,2251,1077],{},[2176,2253,2254,2257,2260],{},[2194,2255,2256],{},"Template logo missing from the output",[2194,2258,2259],{},"openpyxl drops loaded images",[2194,2261,2262],{},"Re-add images in the script.",[2176,2264,2265,2273,2276],{},[2194,2266,2267,2270,2271],{},[163,2268,2269],{},"ImportError"," on ",[163,2272,189],{},[2194,2274,2275],{},"Pillow not installed",[2194,2277,2278,18],{},[163,2279,2280],{},"pip install pillow",[2176,2282,2283,2286,2289],{},[2194,2284,2285],{},"Offsets have no effect",[2194,2287,2288],{},"Offsets given in pixels",[2194,2290,2291,2292,18],{},"Convert with ",[163,2293,2294],{},"pixels_to_EMU",[2176,2296,2297,2300,2303],{},[2194,2298,2299],{},"Image not in the printed output",[2194,2301,2302],{},"Falls outside the print area",[2194,2304,2305,2306,2309],{},"Set ",[163,2307,2308],{},"ws.print_area"," to include it.",[151,2311,2313],{"id":2312},"performance-and-scale-notes","Performance and scale notes",[10,2315,2316],{},"Images are embedded, not referenced, so each one adds its full byte size to the workbook. A 2 MB PNG logo on twelve sheets is 24 MB of report — and the same logo at 40 KB is 480 KB, which nobody notices.",[10,2318,2319,2320,2323,2324,2327],{},"Three habits keep report files small. ",[336,2321,2322],{},"Resize the source, not just the display."," Setting ",[163,2325,2326],{},"img.width"," scales the rendering while embedding the original bytes, so a 3,000-pixel-wide logo displayed at 180 pixels still costs its full size. Downscale the file once:",[156,2329,2331],{"className":193,"code":2330,"language":195,"meta":161,"style":161},"from PIL import Image as PILImage\n\ndef prepare_asset(src, dest, max_width=400):\n    \"\"\"Downscale a source image once so every report embeds a small file.\"\"\"\n    with PILImage.open(src) as image:\n        if image.width > max_width:\n            ratio = max_width \u002F image.width\n            image = image.resize((max_width, round(image.height * ratio)),\n                                 PILImage.LANCZOS)\n        image.save(dest, optimize=True)\n    return dest\n",[163,2332,2333,2347,2351,2367,2372,2384,2398,2413,2433,2443,2458],{"__ignoreMap":161},[166,2334,2335,2337,2339,2341,2343,2345],{"class":168,"line":169},[166,2336,203],{"class":202},[166,2338,362],{"class":361},[166,2340,365],{"class":202},[166,2342,368],{"class":206},[166,2344,371],{"class":202},[166,2346,374],{"class":206},[166,2348,2349],{"class":168,"line":216},[166,2350,233],{"emptyLinePlaceholder":232},[166,2352,2353,2355,2358,2361,2363,2365],{"class":168,"line":229},[166,2354,383],{"class":202},[166,2356,2357],{"class":386}," prepare_asset",[166,2359,2360],{"class":206},"(src, dest, max_width",[166,2362,242],{"class":202},[166,2364,1271],{"class":361},[166,2366,405],{"class":206},[166,2368,2369],{"class":168,"line":236},[166,2370,2371],{"class":176},"    \"\"\"Downscale a source image once so every report embeds a small file.\"\"\"\n",[166,2373,2374,2376,2379,2381],{"class":168,"line":248},[166,2375,454],{"class":202},[166,2377,2378],{"class":206}," PILImage.open(src) ",[166,2380,371],{"class":202},[166,2382,2383],{"class":206}," image:\n",[166,2385,2386,2389,2392,2395],{"class":168,"line":259},[166,2387,2388],{"class":202},"        if",[166,2390,2391],{"class":206}," image.width ",[166,2393,2394],{"class":202},">",[166,2396,2397],{"class":206}," max_width:\n",[166,2399,2400,2403,2405,2408,2410],{"class":168,"line":276},[166,2401,2402],{"class":206},"            ratio ",[166,2404,242],{"class":202},[166,2406,2407],{"class":206}," max_width ",[166,2409,491],{"class":202},[166,2411,2412],{"class":206}," image.width\n",[166,2414,2415,2418,2420,2423,2425,2428,2430],{"class":168,"line":281},[166,2416,2417],{"class":206},"            image ",[166,2419,242],{"class":202},[166,2421,2422],{"class":206}," image.resize((max_width, ",[166,2424,524],{"class":361},[166,2426,2427],{"class":206},"(image.height ",[166,2429,530],{"class":202},[166,2431,2432],{"class":206}," ratio)),\n",[166,2434,2435,2438,2441],{"class":168,"line":298},[166,2436,2437],{"class":206},"                                 PILImage.",[166,2439,2440],{"class":361},"LANCZOS",[166,2442,295],{"class":206},[166,2444,2445,2448,2451,2453,2456],{"class":168,"line":309},[166,2446,2447],{"class":206},"        image.save(dest, ",[166,2449,2450],{"class":620},"optimize",[166,2452,242],{"class":202},[166,2454,2455],{"class":361},"True",[166,2457,295],{"class":206},[166,2459,2460,2462],{"class":168,"line":475},[166,2461,593],{"class":202},[166,2463,2464],{"class":206}," dest\n",[10,2466,2467,2470],{},[336,2468,2469],{},"Choose the format deliberately."," PNG for logos and anything with flat colour or transparency; JPEG for photographs, where it is several times smaller. A screenshot saved as JPEG looks blurry around text, and a photograph saved as PNG is needlessly large.",[10,2472,2473,2476,2477,2480],{},[336,2474,2475],{},"Insert each distinct image once per workbook where you can."," openpyxl embeds a separate copy per ",[163,2478,2479],{},"add_image"," call, so a logo repeated on twelve sheets is stored twelve times. Where the file size matters more than the repetition, put the logo on the first sheet only and rely on a header row elsewhere.",[10,2482,2483,2484,2487,2488,18],{},"For very large reports, note that images cannot be added in ",[163,2485,2486],{},"write_only"," mode — that mode streams rows and never holds the drawing collection. Stream the data first, then re-open the workbook normally to add images, which is cheap because the second pass touches only the drawing parts. The streaming approach itself is covered in ",[14,2489,2491],{"href":2490},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fwrite-large-dataframes-to-excel-with-write-only-mode\u002F","writing large DataFrames with write-only mode",[151,2493,2495],{"id":2494},"conclusion","Conclusion",[10,2497,2498,2499,2501,2502,2504,2505,2507],{},"Placing an image well comes down to three things. Size it from one dimension and derive the other from the source aspect ratio, so it is never stretched. Anchor it with a ",[163,2500,61],{}," and an explicit extent when it is a logo that must keep its proportions, or a ",[163,2503,117],{}," when it is a chart image meant to fill a region. And remember the units differ at every level — pixels for images, points for row heights, EMU for offsets at 9,525 to the pixel, and zero-based indexes in ",[163,2506,1060],{}," where the rest of openpyxl is one-based. Then re-add every image on every run, because openpyxl will not carry a template's images through.",[151,2509,2511],{"id":2510},"frequently-asked-questions","Frequently asked questions",[10,2513,2514,2517],{},[336,2515,2516],{},"What units are the width and height in?","\nPixels, at 96 dots per inch. openpyxl converts them to the EMU units the file format uses, so you set a sensible pixel size and it handles the rest.",[10,2519,2520,2523,2524,330,2526,2528],{},[336,2521,2522],{},"Why is my logo stretched?","\nYou set ",[163,2525,329],{},[163,2527,333],{}," independently. Compute one from the other using the source image's aspect ratio, which Pillow reports, so the proportions are preserved.",[10,2530,2531,2534,2535,2537],{},[336,2532,2533],{},"Why did the image move when I widened a column?","\nThe default anchor ties the image to a cell, so it follows that cell as the layout changes. Use a ",[163,2536,61],{}," with an explicit extent to keep the size fixed, or accept the movement as intended behaviour.",[10,2539,2540,2543,2544,2546,2547,617,2549,617,2551,330,2554,2557,2558,18],{},[336,2541,2542],{},"How do I place an image at an exact offset within a cell?","\nBuild an ",[163,2545,1060],{}," with ",[163,2548,998],{},[163,2550,888],{},[163,2552,2553],{},"colOff",[163,2555,2556],{},"rowOff"," values. The offsets are in EMU, where 9,525 EMU is one pixel, so convert your pixel offset with ",[163,2559,2294],{},[10,2561,2562,2565],{},[336,2563,2564],{},"Do images survive an openpyxl round trip?","\nNo. openpyxl drops images that were already in a workbook when it loads it, so re-add every image your output needs rather than assuming the ones in a template will persist.",[151,2567,2569],{"id":2568},"related","Related",[1913,2571,2572,2578,2585,2591,2598],{},[1916,2573,2574,2575,2577],{},"Up to the parent: ",[14,2576,17],{"href":16}," — the insertion basics.",[1916,2579,2580,2584],{},[14,2581,2583],{"href":2582},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fadd-logo-image-to-excel-report-with-openpyxl\u002F","Add a Logo Image to an Excel Report with openpyxl"," — the branding pass this refines.",[1916,2586,2587,2590],{},[14,2588,2589],{"href":1754},"Embed a matplotlib Chart in an Excel Report"," — generating the images this places.",[1916,2592,2593,2597],{},[14,2594,2596],{"href":2595},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-column-width-and-row-height-in-openpyxl\u002F","Set Column Width and Row Height in openpyxl"," — the dimensions images sit inside.",[1916,2599,2600,2604],{},[14,2601,2603],{"href":2602},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fpopulate-excel-template-without-losing-formatting\u002F","Populate an Excel Template Without Losing Formatting"," — why a template's images need re-adding.",[2606,2607,2608],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":161,"searchDepth":216,"depth":216,"links":2610},[2611,2612,2613,2614,2615,2616,2617,2618,2619,2620],{"id":153,"depth":216,"text":154},{"id":323,"depth":216,"text":324},{"id":716,"depth":216,"text":717},{"id":1248,"depth":216,"text":1249},{"id":1758,"depth":216,"text":1759},{"id":2167,"depth":216,"text":2168},{"id":2312,"depth":216,"text":2313},{"id":2494,"depth":216,"text":2495},{"id":2510,"depth":216,"text":2511},{"id":2568,"depth":216,"text":2569},"2026-08-15","Place a logo or chart image exactly where you want it — pixel sizing, aspect ratio, cell and offset anchors, and why an image moves when a column is resized.","md",[2625,2627,2629,2631,2633],{"q":2516,"a":2626},"Pixels, at 96 dots per inch. openpyxl converts them to the EMU units the file format uses, so you set a sensible pixel size and it handles the rest.",{"q":2522,"a":2628},"You set width and height independently. Compute one from the other using the source image's aspect ratio, which Pillow reports, so the proportions are preserved.",{"q":2533,"a":2630},"The default anchor ties the image to a cell, so it follows that cell as the layout changes. Use a OneCellAnchor with an explicit extent to keep the size fixed, or accept the movement as intended behaviour.",{"q":2542,"a":2632},"Build an AnchorMarker with col, row and colOff, rowOff values. The offsets are in EMU, where 9525 EMU is one pixel, so convert your pixel offset by multiplying.",{"q":2564,"a":2634},"No. openpyxl drops images that were already in a workbook when it loads it, so re-add every image your output needs rather than assuming the ones in a template will persist.",{},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fresize-and-position-images-in-excel-with-openpyxl",{"title":5,"description":2638},"Control image size and placement in Python: width and height in pixels, preserving aspect ratio, OneCellAnchor and TwoCellAnchor, EMU offsets, and multi-image layouts.","resize-and-position-images-in-excel-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fresize-and-position-images-in-excel-with-openpyxl\u002Findex","how-to","gOFmIyLo9auqfIx8RWgPhtxMO7JANhlzy_csgVKk5Ks",[2644,2647],{"title":2589,"path":2645,"stem":2646,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-a-matplotlib-chart-in-an-excel-report","formatting-and-charting-excel-reports-with-python\u002Finserting-images-and-logos-into-excel\u002Fembed-a-matplotlib-chart-in-an-excel-report\u002Findex",{"title":2648,"path":2649,"stem":2650,"children":-1},"Protecting and Sharing Excel Workbooks","\u002Fformatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks","formatting-and-charting-excel-reports-with-python\u002Fprotecting-and-sharing-excel-workbooks\u002Findex",1786800027185]