diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,25 @@
 
+6.7
+---
+
+API changes:
+
+* `Graphics.Vty.Image`: added `rewriteAttrs` for performing arbitrary
+  attribute transformations over images
+
+Other improvements:
+
+* Improved image performance by re-using known text string widths
+  (thanks Andrzej Rybczak)
+
+6.6
+---
+
+* Added support for horizontal scrolling inputs. The `Button` type in
+  `Graphics.Vty.Input.Events` got new constructors `BScrollLeft` and
+  `BScrollRight` for Vty backend implementations that support horizontal
+  scrolling inputs.
+
 6.5
 ---
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -148,14 +148,26 @@
 If you decide to contribute, that's great! Here are some guidelines you
 should consider to make submitting patches easier for all concerned:
 
- - Please ensure that the examples and test suites build along with the
-   library by running `build.sh` in the repository.
+ - Patches written completely or partially by AI are unlikely to be
+   accepted. Please disclose any AI use.
+ - Use good commit hygiene: each commit should make a single,
+   independent logical change and its commit message should clearly
+   describe the motivation for that change and what it does at a high
+   level. If a commit changes something that has tests, the commit
+   should also include test changes as well, or add new tests if
+   possible. "WIP" commits, commits that mix up style and substance
+   (e.g. reformatting style while also adding a feature), etc., are to
+   be avoided.
  - If you want to take on big things, talk to me first; let's have a
    design/vision discussion before you start coding. Create a GitHub
    issue and we can use that as the place to hash things out.
  - If you make changes, make them consistent with the syntactic
    conventions already used in the codebase.
  - Please provide Haddock documentation for any changes you make.
+ - Please do NOT include package version changes in your patches.
+   Package version changes are only done at release time when the full
+   scope of a release's changes can be evaluated to determine the
+   appropriate version change.
 
 # Further Reading
 
diff --git a/src/Graphics/Vty/Image.hs b/src/Graphics/Vty/Image.hs
--- a/src/Graphics/Vty/Image.hs
+++ b/src/Graphics/Vty/Image.hs
@@ -42,6 +42,7 @@
   , translate
   , translateX
   , translateY
+  , rewriteAttrs
   -- * Character width functions
   , safeWcwidth
   , safeWcswidth
diff --git a/src/Graphics/Vty/Image/Internal.hs b/src/Graphics/Vty/Image/Internal.hs
--- a/src/Graphics/Vty/Image/Internal.hs
+++ b/src/Graphics/Vty/Image/Internal.hs
@@ -9,6 +9,7 @@
   , imageWidth
   , horizJoin
   , vertJoin
+  , rewriteAttrs
 
   , ppImageStructure
   , clipText
@@ -120,6 +121,26 @@
     -- Any image of zero size equals the empty image.
     | EmptyImage
     deriving (Eq, Generic, Show, Read)
+
+-- | Rewrite all attributes in the image using the specified
+-- transformation.
+rewriteAttrs :: (Attr -> Attr) -> Image -> Image
+rewriteAttrs f i =
+    case i of
+        HorizText { attr = a } ->
+            i { attr = f a }
+        HorizJoin { partLeft = l, partRight = r } ->
+            i { partLeft = rewriteAttrs f l
+              , partRight = rewriteAttrs f r
+              }
+        VertJoin { partTop = t, partBottom = b } ->
+            i { partTop = rewriteAttrs f t
+              , partBottom = rewriteAttrs f b
+              }
+        Crop { croppedImage = c } ->
+            i { croppedImage = rewriteAttrs f c }
+        BGFill {} -> i
+        EmptyImage -> i
 
 -- | pretty print just the structure of an image.
 ppImageStructure :: Image -> String
diff --git a/src/Graphics/Vty/Input/Events.hs b/src/Graphics/Vty/Input/Events.hs
--- a/src/Graphics/Vty/Input/Events.hs
+++ b/src/Graphics/Vty/Input/Events.hs
@@ -42,7 +42,16 @@
 instance NFData Modifier
 
 -- | Mouse buttons.
-data Button = BLeft | BMiddle | BRight | BScrollUp | BScrollDown
+data Button
+    = BLeft
+    | BMiddle
+    | BRight
+    | BScrollUp
+    | BScrollDown
+    | BScrollLeft
+    -- ^ Some terminals (e.g., Kitty, Alacritty) support horizontal
+    -- scrolling in addition to vertical scrolling.
+    | BScrollRight
     deriving (Eq, Show, Read, Ord, Generic)
 
 instance NFData Button
diff --git a/src/Graphics/Vty/PictureToSpans.hs b/src/Graphics/Vty/PictureToSpans.hs
--- a/src/Graphics/Vty/PictureToSpans.hs
+++ b/src/Graphics/Vty/PictureToSpans.hs
@@ -247,7 +247,7 @@
 --
 addMaybeClipped :: forall s . Image -> BlitM s ()
 addMaybeClipped EmptyImage = return ()
-addMaybeClipped (HorizText a textStr ow _cw) = do
+addMaybeClipped (HorizText a textStr ow cw) = do
     -- This assumes that text spans are only 1 row high.
     s <- use skipRows
     when (s < 1) $ do
@@ -258,7 +258,7 @@
         if leftClipped || rightClipped
             then let textStr' = clipText textStr leftClip rightClip
                  in addUnclippedText a textStr'
-            else addUnclippedText a textStr
+            else snocOp (TextSpan a ow cw textStr) =<< use rowOffset
 addMaybeClipped (VertJoin topImage bottomImage _ow oh) = do
     when (imageHeight topImage + imageHeight bottomImage > 0) $
         addMaybeClippedJoin "vert_join" skipRows remainingRows rowOffset
diff --git a/vty.cabal b/vty.cabal
--- a/vty.cabal
+++ b/vty.cabal
@@ -1,5 +1,5 @@
 name:                vty
-version:             6.5
+version:             6.7
 license:             BSD3
 license-file:        LICENSE
 author:              AUTHORS
